diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 349dcc77..36b8e675 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -37,6 +37,7 @@ repos: files: ^nox/ args: [] additional_dependencies: + - attrs - dependency-groups>=1.2 - jinja2 - packaging diff --git a/nox/_option_set.py b/nox/_option_set.py index d0a02976..573da600 100644 --- a/nox/_option_set.py +++ b/nox/_option_set.py @@ -20,7 +20,6 @@ import argparse import collections -import dataclasses import functools from argparse import ArgumentError as ArgumentError # noqa: PLC0414 from argparse import ArgumentParser, Namespace @@ -28,41 +27,37 @@ from typing import Any, Literal import argcomplete +import attrs +import attrs.validators as av + +av_opt_str = av.optional(av.instance_of(str)) +av_opt_list_str = av.optional( + av.deep_iterable( + member_validator=av.instance_of(str), + iterable_validator=attrs.validators.instance_of(list), + ) +) +av_bool = av.instance_of(bool) -# Python 3.10+ has slots=True (or attrs does), also kwonly=True -@dataclasses.dataclass +@attrs.define(slots=True, kw_only=True) class NoxOptions: - __slots__ = ( - "default_venv_backend", - "envdir", - "error_on_external_run", - "error_on_missing_interpreters", - "force_venv_backend", - "keywords", - "pythons", - "report", - "reuse_existing_virtualenvs", - "reuse_venv", - "sessions", - "stop_on_first_error", - "tags", - "verbose", + default_venv_backend: None | str = attrs.field(validator=av_opt_str) + envdir: None | str = attrs.field(validator=av_opt_str) + error_on_external_run: bool = attrs.field(validator=av_bool) + error_on_missing_interpreters: bool = attrs.field(validator=av_bool) + force_venv_backend: None | str = attrs.field(validator=av_opt_str) + keywords: None | list[str] = attrs.field(validator=av_opt_list_str) + pythons: None | list[str] = attrs.field(validator=av_opt_list_str) + report: None | str = attrs.field(validator=av_opt_str) + reuse_existing_virtualenvs: bool = attrs.field(validator=av_bool) + reuse_venv: None | Literal["no", "yes", "never", "always"] = attrs.field( + validator=av.optional(av.in_(["no", "yes", "never", "always"])) ) - default_venv_backend: None | str - envdir: None | str - error_on_external_run: bool - error_on_missing_interpreters: bool - force_venv_backend: None | str - keywords: None | list[str] - pythons: None | list[str] - report: None | str - reuse_existing_virtualenvs: bool - reuse_venv: None | Literal["no", "yes", "never", "always"] - sessions: None | list[str] - stop_on_first_error: bool - tags: None | list[str] - verbose: bool + sessions: None | list[str] = attrs.field(validator=av_opt_list_str) + stop_on_first_error: bool = attrs.field(validator=av_bool) + tags: None | list[str] = attrs.field(validator=av_opt_list_str) + verbose: bool = attrs.field(validator=av_bool) class OptionGroup: diff --git a/pyproject.toml b/pyproject.toml index a179c049..69a96f36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,6 +40,7 @@ classifiers = [ ] dependencies = [ "argcomplete<4,>=1.9.4", + "attrs>=23.1", "colorlog<7,>=2.6.1", "dependency-groups>=1.1", "packaging>=20.9",