Skip to content

typerdrive Settings modules

typerdrive.settings.attach

Provide a decorator that attaches the typerdrive settings to a typer command function.

Attributes

Classes

Functions

attach_settings

attach_settings(
    settings_model: type[BaseModel],
    *,
    validation: Validation = Validation.BEFORE,
    persist: bool = False,
    show: bool = False,
) -> Callable[
    [ContextFunction[P, T]], ContextFunction[P, T]
]

Attach the typerdrive settings to the decorated typer command function.

Parameters:

Name Type Description Default
validation Validation

Indicates when validation should happen in the decorator:

    - If `BEFORE`, validate before calling the function
    - If `AFTER`, validate after calling the function
    - If `BOTH`, validate before and after calling the function
    - If `NEVER`, don't validate
BEFORE
persist bool

If set, the settings values will be stored in the settings file

False
show bool

If set, show the current settings after running the function

False
Source code in src/typerdrive/settings/attach.py
def attach_settings(
    settings_model: type[BaseModel],
    *,
    validation: Validation = Validation.BEFORE,
    persist: bool = False,
    show: bool = False,
) -> Callable[[ContextFunction[P, T]], ContextFunction[P, T]]:
    """
    Attach the `typerdrive` settings to the decorated `typer` command function.

    Parameters:
        validation: Indicates when validation should happen in the decorator:

                    - If `BEFORE`, validate before calling the function
                    - If `AFTER`, validate after calling the function
                    - If `BOTH`, validate before and after calling the function
                    - If `NEVER`, don't validate

        persist:    If set, the settings values will be stored in the settings file
        show:       If set, show the current settings after running the function
    """
    def _decorate(func: ContextFunction[P, T]) -> ContextFunction[P, T]:
        manager_param_key: str | None = None
        settings_param_key: str | None = None
        for key in func.__annotations__.keys():
            if func.__annotations__[key] is settings_model:
                func.__annotations__[key] = Annotated[settings_model | None, CloakingDevice]
                settings_param_key = key
            elif func.__annotations__[key] is SettingsManager:
                func.__annotations__[key] = Annotated[SettingsManager | None, CloakingDevice]
                manager_param_key = key

        # TODO: Figure out how we can make the ctx param optional for the wrapped function
        @wraps(func)
        def wrapper(ctx: typer.Context, *args: P.args, **kwargs: P.kwargs) -> T:
            manager: SettingsManager = SettingsManager(settings_model)

            if validation & Validation.BEFORE:
                SettingsError.require_condition(
                    len(manager.invalid_warnings) == 0,
                    f"Initial settings are invalid: {manager.invalid_warnings}",
                )
            to_context(ctx, "settings_manager", manager)

            if settings_param_key:
                kwargs[settings_param_key] = manager.settings_instance

            if manager_param_key:
                kwargs[manager_param_key] = manager

            ret_val = func(ctx, *args, **kwargs)

            if validation & Validation.AFTER:
                with SettingsError.handle_errors("Final settings are invalid"):
                    manager.validate()

            if persist:
                manager.save()
            if show:
                terminal_message(
                    manager.pretty(),
                    subject="Current settings",
                    footer=f"saved to {manager.settings_path}" if persist else None,
                )

            return ret_val

        return wrapper

    return _decorate

get_settings

get_settings(ctx: Context, type_hint: type[ST]) -> ST

Get the settings instance from the TyperdriveContext.

Source code in src/typerdrive/settings/attach.py
def get_settings[ST: BaseModel](
    ctx: typer.Context,
    type_hint: type[ST],
) -> ST:
    """
    Get the settings instance from the `TyperdriveContext`.
    """
    return SettingsError.ensure_type(
        get_settings_manager(ctx).settings_instance,
        type_hint,
        f"Settings instance doesn't match expected {type_hint=}",
    )

get_settings_manager

get_settings_manager(ctx: Context) -> SettingsManager

Retrieve the SettingsManager from the TyperdriveContext.

Source code in src/typerdrive/settings/attach.py
def get_settings_manager(ctx: typer.Context) -> SettingsManager:
    """
    Retrieve the `SettingsManager` from the `TyperdriveContext`.
    """
    with SettingsError.handle_errors("Settings are not bound to the context. Use the @attach_settings() decorator"):
        mgr: Any = from_context(ctx, "settings_manager")
    return SettingsError.ensure_type(
        mgr,
        SettingsManager,
        "Item in user context at `settings_manager` was not a SettingsManager",
    )

get_settings_value

get_settings_value(ctx: Context, settings_key: str) -> Any

Get a particular settings value from the TyperdriveContext.

Source code in src/typerdrive/settings/attach.py
def get_settings_value(ctx: typer.Context, settings_key: str) -> Any:
    """
    Get a particular settings value from the `TyperdriveContext`.
    """
    instance: BaseModel = get_settings_manager(ctx).settings_instance
    ret = getattr(instance, settings_key, Sentinel.MISSING)
    SettingsError.require_condition(
        ret is not Sentinel.MISSING,
        f"Settings instance doesn't have field {settings_key}",
    )
    return ret

typerdrive.settings.commands

Provide commands that can be added to a typer app to manage settings.

Classes

Functions

add_bind

add_bind(cli: Typer, settings_model: type[BaseModel])

Add the bind command to the given typer app.

Source code in src/typerdrive/settings/commands.py
def add_bind(cli: typer.Typer, settings_model: type[BaseModel]):
    """
    Add the `bind` command to the given `typer` app.
    """
    opt_defs: list[OptDef] = []
    for name, field_info in settings_model.model_fields.items():
        default = field_info.default if field_info.default is not PydanticUndefined else Sentinel.NOT_GIVEN
        param_type: type[Any] = SettingsError.enforce_defined(
            field_info.annotation, "Option type may not be `None`"
        )  # TODO: Figure out if this can even be triggered
        opt_kwargs: dict[str, Any] = dict(
            name=name,
            param_type=param_type,
            default=default,
            help=field_info.description,
            show_default=True,
        )
        if issubclass(param_type, BaseModel):
            model_type = param_type
            opt_kwargs["parser"] = make_parser(model_type)
            opt_kwargs["metavar"] = pretty_model(model_type)

        opt_defs.append(
            OptDef(**opt_kwargs)
        )
    build_command(
        cli,
        bind,
        *opt_defs,
        decorators=[
            DecDef(
                dec_func=handle_errors,
                dec_args=["Failed to bind settings"],
                dec_kwargs=dict(
                    handle_exc_class=SettingsError,
                    unwrap_message=False,
                    debug=True,
                ),
                is_simple=False,
            ),
            DecDef(
                dec_func=attach_settings,
                dec_args=[settings_model],
                dec_kwargs=dict(validation=Validation.AFTER, persist=True, show=True),
                is_simple=False,
            ),
        ],
        include_context=True,
    )

add_reset

add_reset(cli: Typer, settings_model: type[BaseModel])

Add the reset command to the given typer app.

Source code in src/typerdrive/settings/commands.py
def add_reset(cli: typer.Typer, settings_model: type[BaseModel]):
    """
    Add the `reset` command to the given `typer` app.
    """
    build_command(
        cli,
        reset,
        decorators=[
            DecDef(
                dec_func=handle_errors,
                dec_args=["Failed to reset settings"],
                dec_kwargs=dict(
                    handle_exc_class=SettingsError,
                    unwrap_message=False,
                    debug=True,
                ),
                is_simple=False,
            ),
            DecDef(
                dec_func=attach_settings,
                dec_args=[settings_model],
                dec_kwargs=dict(validation=Validation.NEVER, persist=True, show=True),
                is_simple=False,
            ),
        ],
        include_context=True,
    )

add_settings_subcommand

add_settings_subcommand(
    cli: Typer, settings_model: type[BaseModel]
)

Add all settings commands to the given app.

Source code in src/typerdrive/settings/commands.py
def add_settings_subcommand(cli: typer.Typer, settings_model: type[BaseModel]):
    """
    Add all `settings` commands to the given app.
    """
    settings_cli = typer.Typer(help="Manage settings for the app")

    for cmd in [add_bind, add_update, add_unset, add_reset, add_show]:
        cmd(settings_cli, settings_model)

    cli.add_typer(settings_cli, name="settings")

add_show

add_show(cli: Typer, settings_model: type[BaseModel])

Add the show command to the given typer app.

Source code in src/typerdrive/settings/commands.py
def add_show(cli: typer.Typer, settings_model: type[BaseModel]):
    """
    Add the `show` command to the given `typer` app.
    """
    build_command(
        cli,
        show,
        decorators=[
            DecDef(
                dec_func=handle_errors,
                dec_args=["Failed to show settings"],
                dec_kwargs=dict(
                    handle_exc_class=SettingsError,
                    unwrap_message=False,
                    debug=True,
                ),
                is_simple=False,
            ),
            DecDef(
                dec_func=attach_settings,
                dec_args=[settings_model],
                dec_kwargs=dict(validation=Validation.NEVER, persist=False, show=True),
                is_simple=False,
            ),
        ],
        include_context=True,
    )

add_unset

add_unset(cli: Typer, settings_model: type[BaseModel])

Add the unset command to the given typer app.

Source code in src/typerdrive/settings/commands.py
def add_unset(cli: typer.Typer, settings_model: type[BaseModel]):
    """
    Add the `unset` command to the given `typer` app.
    """
    opt_defs: list[OptDef] = []
    for name, field_info in settings_model.model_fields.items():
        opt_defs.append(
            OptDef(
                name=name,
                param_type=bool,
                default=False,
                help=field_info.description,
                show_default=True,
                override_name=name,
            )
        )
    build_command(
        cli,
        unset,
        *opt_defs,
        decorators=[
            DecDef(
                dec_func=handle_errors,
                dec_args=["Failed to unset settings"],
                dec_kwargs=dict(
                    handle_exc_class=SettingsError,
                    unwrap_message=False,
                    debug=True,
                ),
                is_simple=False,
            ),
            DecDef(
                dec_func=attach_settings,
                dec_args=[settings_model],
                dec_kwargs=dict(validation=Validation.NEVER, persist=True, show=True),
                is_simple=False,
            ),
        ],
        include_context=True,
    )

add_update

add_update(cli: Typer, settings_model: type[BaseModel])

Add the update command to the given typer app.

Source code in src/typerdrive/settings/commands.py
def add_update(cli: typer.Typer, settings_model: type[BaseModel]):
    """
    Add the `update` command to the given `typer` app.
    """
    opt_defs: list[OptDef] = []
    for name, field_info in settings_model.model_fields.items():
        param_type: type[Any] = SettingsError.enforce_defined(field_info.annotation, "Option type may not be `None`")
        default: None | bool = None
        opt_kwargs: dict[str, Any] = dict(
            name=name,
            param_type=param_type | None,
            default=default,
            help=field_info.description,
            show_default=True,
        )
        if param_type is bool:
            default = field_info.default
        elif issubclass(param_type, BaseModel):
            model_type = param_type
            opt_kwargs["parser"] = make_parser(model_type)
            opt_kwargs["metavar"] = pretty_model(model_type)

        opt_defs.append(OptDef(**opt_kwargs))
    build_command(
        cli,
        update,
        *opt_defs,
        decorators=[
            DecDef(
                dec_func=handle_errors,
                dec_args=["Failed to update settings"],
                dec_kwargs=dict(
                    handle_exc_class=SettingsError,
                    unwrap_message=False,
                    debug=True,
                ),
                is_simple=False,
            ),
            DecDef(
                dec_func=attach_settings,
                dec_args=[settings_model],
                dec_kwargs=dict(validation=Validation.NEVER, persist=True, show=True),
                is_simple=False,
            ),
        ],
        include_context=True,
    )

bind

bind(ctx: Context)

Bind all settings for the app.

Source code in src/typerdrive/settings/commands.py
def bind(ctx: typer.Context):
    """
    Bind all settings for the app.
    """
    __manager: SettingsManager = get_settings_manager(ctx)
    excluded_locals = ["__manager", "ctx"]
    settings_values = {k: v for (k, v) in locals().items() if k not in excluded_locals}
    __manager.update(**settings_values)

reset

reset(ctx: Context)

Remove all settings from the app.

Source code in src/typerdrive/settings/commands.py
def reset(ctx: typer.Context):
    """
    Remove all settings from the app.
    """
    typer.confirm("Are you sure you want to reset your settings?", abort=True)
    __manager: SettingsManager = get_settings_manager(ctx)
    __manager.reset()

show

show(ctx: Context)

Show the current app's settings.

Source code in src/typerdrive/settings/commands.py
def show(ctx: typer.Context):  # pyright: ignore[reportUnusedParameter]
    """
    Show the current app's settings.
    """

unset

unset(ctx: Context)

Remove some settings from the app.

Source code in src/typerdrive/settings/commands.py
def unset(ctx: typer.Context):
    """
    Remove some settings from the app.
    """
    __manager: SettingsManager = get_settings_manager(ctx)
    excluded_locals = ["__manager", "ctx"]
    settings_values = {k: v for (k, v) in locals().items() if k not in excluded_locals and v}
    __manager.unset(*settings_values.keys())

update

update(ctx: Context)

Update some settings for the app.

Source code in src/typerdrive/settings/commands.py
def update(ctx: typer.Context):
    """
    Update some settings for the app.
    """
    __manager: SettingsManager = get_settings_manager(ctx)
    excluded_locals = ["__manager", "ctx"]
    settings_values = {k: v for (k, v) in locals().items() if k not in excluded_locals and v is not None}
    __manager.update(**settings_values)

typerdrive.settings.exceptions

Provide exceptions specific to the settings feature of typerdrive.

Classes

SettingsDisplayError

Bases: SettingsError

Indicates that there was a problem showing the settings.

Source code in src/typerdrive/settings/exceptions.py
class SettingsDisplayError(SettingsError):
    """
    Indicates that there was a problem showing the settings.
    """

SettingsError

Bases: TyperdriveError

The base exception for settings errors.

Source code in src/typerdrive/settings/exceptions.py
class SettingsError(TyperdriveError):
    """
    The base exception for settings errors.
    """
    exit_code: ExitCode = ExitCode.GENERAL_ERROR
Attributes
exit_code class-attribute instance-attribute
exit_code: ExitCode = GENERAL_ERROR

SettingsInitError

Bases: SettingsError

Indicates that there was a problem initializing the settings.

Source code in src/typerdrive/settings/exceptions.py
class SettingsInitError(SettingsError):
    """
    Indicates that there was a problem initializing the settings.
    """

SettingsResetError

Bases: SettingsError

Indicates that there was a problem resetting a settings value.

Source code in src/typerdrive/settings/exceptions.py
class SettingsResetError(SettingsError):
    """
    Indicates that there was a problem resetting a settings value.
    """

SettingsSaveError

Bases: SettingsError

Indicates that there was a problem saving the settings to disk.

Source code in src/typerdrive/settings/exceptions.py
class SettingsSaveError(SettingsError):
    """
    Indicates that there was a problem saving the settings to disk.
    """

SettingsUnsetError

Bases: SettingsError

Indicates that there was a problem unsetting a settings value.

Source code in src/typerdrive/settings/exceptions.py
class SettingsUnsetError(SettingsError):
    """
    Indicates that there was a problem unsetting a settings value.
    """

SettingsUpdateError

Bases: SettingsError

Indicates that there was a problem updating a settings value.

Source code in src/typerdrive/settings/exceptions.py
class SettingsUpdateError(SettingsError):
    """
    Indicates that there was a problem updating a settings value.
    """

typerdrive.settings.manager

Provide a class for managing the typerdrive settings feature.

Classes

SettingsManager

Manage settings for the typerdrive app.

Source code in src/typerdrive/settings/manager.py
class SettingsManager:
    """
    Manage settings for the `typerdrive` app.
    """

    settings_model: type[BaseModel]
    """ A `pydantic` model _type_ that defines the app's settings """

    settings_path: Path
    """ The path to the file where settings are persisted """

    invalid_warnings: dict[str, str]
    """ Tracks which fields of the settings instance are invalid """

    settings_instance: BaseModel  # pyright: ignore[reportUninitializedInstanceVariable]
    """ An instance of the `settings_model` that holds the app's current settings """

    def __init__(self, settings_model: type[BaseModel]):
        config: TyperdriveConfig = get_typerdrive_config()

        self.settings_model = settings_model
        self.settings_path = config.settings_path
        self.invalid_warnings = {}

        with SettingsInitError.handle_errors("Failed to initialize settings"):
            settings_values: dict[str, Any] = {}
            if self.settings_path.exists():
                settings_values = json.loads(self.settings_path.read_text())
            self.construct_instance(**settings_values)

    def construct_instance(self, **settings_values: Any):
        try:
            self.settings_instance = self.settings_model(**settings_values)
            self.invalid_warnings = {}
        except ValidationError as err:
            self.settings_instance = construct_permissive(self.settings_model, **settings_values)
            self.set_warnings(err)

    def set_warnings(self, err: ValidationError):
        """
        Given a `ValidationError`, extract the field names and messages to the `invalid_warnings` dict.
        """
        self.invalid_warnings = {}
        for data in err.errors():
            key: str = cast(str, data["loc"][0])
            message = data["msg"]
            self.invalid_warnings[key] = message

    def update(self, **settings_values: Any):
        """
        Update the app settings given the provided key/value pairs.

        If validation fails, the `invalid_warngings` will be updated, but all valid fields will remain set.
        """
        logger.debug(f"Updating settings with {settings_values}")

        with SettingsUpdateError.handle_errors("Failed to update settings"):
            combined_settings = {**self.settings_instance.model_dump(mode="json"), **settings_values}
            self.construct_instance(**combined_settings)

    def unset(self, *unset_keys: str):
        """
        Remove all the settings corresponding to the provided keys.
        """
        logger.debug(f"Unsetting keys {unset_keys}")

        with SettingsUnsetError.handle_errors("Failed to remove keys"):
            settings_values = {k: v for (k, v) in self.settings_instance.model_dump(mode="json").items() if k not in unset_keys}
            self.construct_instance(**settings_values)

    def reset(self):
        """
        Reset the settings back to defaults.
        """
        logger.debug("Resetting all settings")

        with SettingsResetError.handle_errors("Failed to reset settings"):
            self.construct_instance()

    def validate(self):
        """
        Validate the current settings values.

        If invalid, `ValidationError` exceptions will be raised
        """
        self.settings_model(**self.settings_instance.model_dump(mode="json"))

    def pretty(self, with_style: bool = True) -> RenderableType:
        """
        Return a pretty representation of the settings.
        """
        (bold_, _bold) = ("[bold]", "[/bold]") if with_style else ("", "")
        (italic_, _italic) = ("[italic]", "[/italic]") if with_style else ("", "")
        (underline_, _underline) = ("[underline]", "[/underline]") if with_style else ("", "")
        (red_, _red) = ("[red]", "[/red]") if with_style else ("", "")
        (blue_, _blue) = ("[blue]", "[/blue]") if with_style else ("", "")

        nested: list[str] = []

        items: list[RenderableType] = []

        values_table: Table = Table(
            title="Settings Values",
            title_style="bold underline",
            title_justify="left",
            box=None,
        )
        values_table.add_column(justify="right", style="bold", no_wrap=True)
        values_table.add_column(justify="left", style="italic", no_wrap=True)
        values_table.add_column(justify="center")
        values_table.add_column(justify="left", no_wrap=True)

        for (field_name, field_info) in self.settings_instance.__class__.model_fields.items():
            if field_name == "invalid_warning":
                continue
            try:
                field_string = str(getattr(self.settings_instance, field_name))
            except AttributeError:
                field_string = "<UNSET>"
            if field_name in self.invalid_warnings:
                field_string = f"{red_}{field_string}{_red}"
            annotation = SettingsDisplayError.enforce_defined(field_info.annotation, f"The field info annotation for {field_info} was not defined!")
            field_type: str = f"{italic_}{annotation.__name__}{_italic}"
            if issubclass(annotation, BaseModel):
                field_type = f"{blue_}{field_type}*{_blue}"
                nested.append(pretty_field_info(field_info))
            values_table.add_row(
                dasherize(field_name),
                field_type,
                "->",
                field_string
            )

        items.append(values_table)

        if self.invalid_warnings:
            items.append("")
            items.append("")
            invalid_table: Table = Table(
                title="Invalid Values",
                title_style="bold underline",
                title_justify="left",
                box=None,
            )
            invalid_table.add_column(justify="right", style="bold", no_wrap=True)
            invalid_table.add_column(justify="center")
            invalid_table.add_column(justify="left", no_wrap=True)

            for (field_name, invalid_warning) in self.invalid_warnings.items():
                invalid_table.add_row(
                    dasherize(field_name),
                    "->",
                    invalid_warning,
                )

            items.append(invalid_table)


        if nested:
            items.append("")
            items.append("")
            items.append(f"{blue_}{bold_}{underline_}{_underline}{_bold}{_blue}")
            nested_table: Table = Table(
                title="*Nested Model Types",
                title_style="blue bold underline",
                title_justify="left",
                box=None,
            )
            nested_table.add_column(style="bold")
            for model in nested:
                nested_table.add_row(model)

            items.append(nested_table)

        return Group(*items)


    def save(self):
        """
        Write the current settings to disk.
        """
        logger.debug(f"Saving settings to {self.settings_path}")

        with SettingsSaveError.handle_errors(f"Failed to save settings to {self.settings_path}"):
            self.settings_path.parent.mkdir(parents=True, exist_ok=True)
            self.settings_path.write_text(self.settings_instance.model_dump_json(indent=2))
Attributes
invalid_warnings instance-attribute
invalid_warnings: dict[str, str] = {}

Tracks which fields of the settings instance are invalid

settings_instance instance-attribute
settings_instance: BaseModel

An instance of the settings_model that holds the app's current settings

settings_model instance-attribute
settings_model: type[BaseModel] = settings_model

A pydantic model type that defines the app's settings

settings_path instance-attribute
settings_path: Path = settings_path

The path to the file where settings are persisted

Functions
__init__
__init__(settings_model: type[BaseModel])
Source code in src/typerdrive/settings/manager.py
def __init__(self, settings_model: type[BaseModel]):
    config: TyperdriveConfig = get_typerdrive_config()

    self.settings_model = settings_model
    self.settings_path = config.settings_path
    self.invalid_warnings = {}

    with SettingsInitError.handle_errors("Failed to initialize settings"):
        settings_values: dict[str, Any] = {}
        if self.settings_path.exists():
            settings_values = json.loads(self.settings_path.read_text())
        self.construct_instance(**settings_values)
construct_instance
construct_instance(**settings_values: Any)
Source code in src/typerdrive/settings/manager.py
def construct_instance(self, **settings_values: Any):
    try:
        self.settings_instance = self.settings_model(**settings_values)
        self.invalid_warnings = {}
    except ValidationError as err:
        self.settings_instance = construct_permissive(self.settings_model, **settings_values)
        self.set_warnings(err)
pretty
pretty(with_style: bool = True) -> RenderableType

Return a pretty representation of the settings.

Source code in src/typerdrive/settings/manager.py
def pretty(self, with_style: bool = True) -> RenderableType:
    """
    Return a pretty representation of the settings.
    """
    (bold_, _bold) = ("[bold]", "[/bold]") if with_style else ("", "")
    (italic_, _italic) = ("[italic]", "[/italic]") if with_style else ("", "")
    (underline_, _underline) = ("[underline]", "[/underline]") if with_style else ("", "")
    (red_, _red) = ("[red]", "[/red]") if with_style else ("", "")
    (blue_, _blue) = ("[blue]", "[/blue]") if with_style else ("", "")

    nested: list[str] = []

    items: list[RenderableType] = []

    values_table: Table = Table(
        title="Settings Values",
        title_style="bold underline",
        title_justify="left",
        box=None,
    )
    values_table.add_column(justify="right", style="bold", no_wrap=True)
    values_table.add_column(justify="left", style="italic", no_wrap=True)
    values_table.add_column(justify="center")
    values_table.add_column(justify="left", no_wrap=True)

    for (field_name, field_info) in self.settings_instance.__class__.model_fields.items():
        if field_name == "invalid_warning":
            continue
        try:
            field_string = str(getattr(self.settings_instance, field_name))
        except AttributeError:
            field_string = "<UNSET>"
        if field_name in self.invalid_warnings:
            field_string = f"{red_}{field_string}{_red}"
        annotation = SettingsDisplayError.enforce_defined(field_info.annotation, f"The field info annotation for {field_info} was not defined!")
        field_type: str = f"{italic_}{annotation.__name__}{_italic}"
        if issubclass(annotation, BaseModel):
            field_type = f"{blue_}{field_type}*{_blue}"
            nested.append(pretty_field_info(field_info))
        values_table.add_row(
            dasherize(field_name),
            field_type,
            "->",
            field_string
        )

    items.append(values_table)

    if self.invalid_warnings:
        items.append("")
        items.append("")
        invalid_table: Table = Table(
            title="Invalid Values",
            title_style="bold underline",
            title_justify="left",
            box=None,
        )
        invalid_table.add_column(justify="right", style="bold", no_wrap=True)
        invalid_table.add_column(justify="center")
        invalid_table.add_column(justify="left", no_wrap=True)

        for (field_name, invalid_warning) in self.invalid_warnings.items():
            invalid_table.add_row(
                dasherize(field_name),
                "->",
                invalid_warning,
            )

        items.append(invalid_table)


    if nested:
        items.append("")
        items.append("")
        items.append(f"{blue_}{bold_}{underline_}{_underline}{_bold}{_blue}")
        nested_table: Table = Table(
            title="*Nested Model Types",
            title_style="blue bold underline",
            title_justify="left",
            box=None,
        )
        nested_table.add_column(style="bold")
        for model in nested:
            nested_table.add_row(model)

        items.append(nested_table)

    return Group(*items)
reset
reset()

Reset the settings back to defaults.

Source code in src/typerdrive/settings/manager.py
def reset(self):
    """
    Reset the settings back to defaults.
    """
    logger.debug("Resetting all settings")

    with SettingsResetError.handle_errors("Failed to reset settings"):
        self.construct_instance()
save
save()

Write the current settings to disk.

Source code in src/typerdrive/settings/manager.py
def save(self):
    """
    Write the current settings to disk.
    """
    logger.debug(f"Saving settings to {self.settings_path}")

    with SettingsSaveError.handle_errors(f"Failed to save settings to {self.settings_path}"):
        self.settings_path.parent.mkdir(parents=True, exist_ok=True)
        self.settings_path.write_text(self.settings_instance.model_dump_json(indent=2))
set_warnings
set_warnings(err: ValidationError)

Given a ValidationError, extract the field names and messages to the invalid_warnings dict.

Source code in src/typerdrive/settings/manager.py
def set_warnings(self, err: ValidationError):
    """
    Given a `ValidationError`, extract the field names and messages to the `invalid_warnings` dict.
    """
    self.invalid_warnings = {}
    for data in err.errors():
        key: str = cast(str, data["loc"][0])
        message = data["msg"]
        self.invalid_warnings[key] = message
unset
unset(*unset_keys: str)

Remove all the settings corresponding to the provided keys.

Source code in src/typerdrive/settings/manager.py
def unset(self, *unset_keys: str):
    """
    Remove all the settings corresponding to the provided keys.
    """
    logger.debug(f"Unsetting keys {unset_keys}")

    with SettingsUnsetError.handle_errors("Failed to remove keys"):
        settings_values = {k: v for (k, v) in self.settings_instance.model_dump(mode="json").items() if k not in unset_keys}
        self.construct_instance(**settings_values)
update
update(**settings_values: Any)

Update the app settings given the provided key/value pairs.

If validation fails, the invalid_warngings will be updated, but all valid fields will remain set.

Source code in src/typerdrive/settings/manager.py
def update(self, **settings_values: Any):
    """
    Update the app settings given the provided key/value pairs.

    If validation fails, the `invalid_warngings` will be updated, but all valid fields will remain set.
    """
    logger.debug(f"Updating settings with {settings_values}")

    with SettingsUpdateError.handle_errors("Failed to update settings"):
        combined_settings = {**self.settings_instance.model_dump(mode="json"), **settings_values}
        self.construct_instance(**combined_settings)
validate
validate()

Validate the current settings values.

If invalid, ValidationError exceptions will be raised

Source code in src/typerdrive/settings/manager.py
def validate(self):
    """
    Validate the current settings values.

    If invalid, `ValidationError` exceptions will be raised
    """
    self.settings_model(**self.settings_instance.model_dump(mode="json"))

Functions