Skip to content

✨ feat(plugin): type the plugin and internal API surfaces - #4014

Merged
gaborbernat merged 1 commit into
tox-dev:mainfrom
gaborbernat:typed-api
Aug 9, 2026
Merged

gaborbernat merged 1 commit into
tox-dev:mainfrom
gaborbernat:typed-api

Conversation

@gaborbernat

@gaborbernat gaborbernat commented Aug 9, 2026 •

Copy link
Copy Markdown
Member

Plugin authors work against surfaces tox types as Any: every conf["key"] read needs a cast, the tox_on_install hook gives no clue what arguments holds, and a typo in options.<name> only surfaces at runtime. 🧩 This change types those surfaces so editors complete them and type checkers verify them, for plugins and for tox itself.

ConfigSet.get(key, of_type) returns a configuration value verified at runtime against its declared type, while the Any-returning conf["key"] stays for compatibility. The tox_on_install hook and Installer.install receive InstallArguments, a named union of what tox installs; Installer gained a second type parameter that defaults to this union, so existing Installer[MyEnv] subclasses keep working. Parsed declares the CLI option surface, add_config accepts default=None and infers value types from callable defaults (removing six of the seven suppressions for astral-sh/ty#2428), persisted JSON documents share a JsonValue alias, and package plus run modes became Literal types. ✨ To keep this true over time, the type environment now runs mypy in strict mode and pyrefly after ty, each as its own command so ty keeps finishing in under a second, and tests/type_check/ pins the inference behavior of the new APIs under all three checkers.

The added checkers caught three bugs: reporting annotated colorama colors as int although they hold ANSI escape strings, a pyproject.toml with a non-table tool.tox or env_base raised a traceback instead of an error, and the structured { product = ... } form accepted a non-list exclude. 🐛 Beyond those, runtime behavior does not change; typing-extensions becomes a runtime dependency below Python 3.13 to supply TypeVar defaults, and each remaining suppression names its error code and the upstream gap it works around.

@gaborbernat
gaborbernat force-pushed the typed-api branch 4 times, most recently from e787498 to 6e24409 Compare August 9, 2026 16:23
@gaborbernat gaborbernat changed the title Type the plugin and internal API surfaces ✨ feat(plugin): type the plugin and internal API surfaces Aug 9, 2026
@gaborbernat
gaborbernat force-pushed the typed-api branch 7 times, most recently from 4cba309 to 00ced99 Compare August 9, 2026 20:02
Replace Any on the surfaces plugins build against:

* ConfigSet.get(key, of_type) reads a config value verified against
  its declared type, replacing cast() around conf["key"] reads
* tox_on_install and Installer type arguments as InstallArguments; a
  second Installer type parameter (defaulting to that union) lets
  subclasses narrow it while Installer[Env] keeps working
* Parsed declares the CLI option surface so options.<name> type-checks
* add_config accepts default=None, infers value types from callable
  defaults, and drops six of the seven ty overload suppressions
* journal, env cache, and execute metadata content is JsonValue;
  structured set_env entries match SetEnvEntry; package modes, run
  modes, and the replacer dispatch use Literal types

The type env now gates mypy --strict and pyrefly next to ty; all
three pass. typing-extensions becomes a runtime dependency below
Python 3.13 for TypeVar defaults. The stricter checks surfaced two
fixes: colorama colors were annotated int yet hold strings, and a
non-table tool.tox or env_base in pyproject.toml no longer crashes.
@gaborbernat
gaborbernat merged commit 7be33b8 into tox-dev:main Aug 9, 2026
30 checks passed
rockygeekz added a commit to ansible/tox-ansible that referenced this pull request Aug 11, 2026
* fix(tests): add result_json to Parsed() calls for tox 4.59 compat

tox 4.59.0 (tox-dev/tox#4014) changed State.__init__ to access
options.parsed.result_json directly instead of using getattr with
a fallback default. This breaks tests that construct Parsed objects
without including result_json, causing AttributeError in 28 tests.

Adding result_json=None to all Parsed() constructor calls in the
test suite fixes the devel test failures.

* style: wrap long Parsed() calls to satisfy line length limit

* refactor(tests): extract _parsed helper to reduce test_plugin line count

Replace 26 verbose multi-line Parsed() calls with a compact _parsed()
helper that sets default override=[] and result_json=None. Brings
the module from 1644 lines back under the 1600 pylint limit.
@gaborbernat
gaborbernat deleted the typed-api branch October 3, 2026 15:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant