diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9738dbad..12f2648f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,7 +4,7 @@ ci: repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-case-conflict - id: check-ast @@ -33,7 +33,7 @@ repos: [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] - repo: https://github.com/pre-commit/mirrors-prettier - rev: "v3.0.2" + rev: "v3.0.3" hooks: - id: prettier types_or: [yaml, html, json] @@ -44,13 +44,8 @@ repos: - id: blacken-docs additional_dependencies: [black==23.7.0] - - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.9.1 - hooks: - - id: black - - repo: https://github.com/codespell-project/codespell - rev: "v2.2.5" + rev: "v2.2.6" hooks: - id: codespell args: ["-L", "sur,nd"] @@ -63,13 +58,14 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.1 + rev: v0.1.3 hooks: - id: ruff args: ["--fix", "--show-fixes"] + - id: ruff-format - repo: https://github.com/scientific-python/cookie - rev: "2023.08.23" + rev: "2023.10.27" hooks: - id: sp-repo-review additional_dependencies: ["repo-review[cli]"] diff --git a/CHANGELOG.md b/CHANGELOG.md index 36b4b6a1..c2bafc65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -358,7 +358,7 @@ complete rewrite, so if you have an application that does use the parsing logic of traitlets you may see changes in behavior, and now have access to more features. There was also a cleanup of what was considered a part of the public API, certain previously exposed utility functions -and types are no longer available. Please see an exhausive list below. +and types are no longer available. Please see an exhaustive list below. ::: seealso `commandline` docs for details about @@ -521,7 +521,7 @@ description. ### API changes This list is auto-generated by `frappuccino`, comparing with traitlets -4.3.3 API and editied for shortness: +4.3.3 API and edited for shortness: ``` The following items are new: diff --git a/docs/source/conf.py b/docs/source/conf.py index 1b3d587b..ca7d0ce6 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -143,7 +143,7 @@ # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. -# html_theme_options = {} +html_theme_options = {"navigation_with_keys": False} # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] @@ -311,7 +311,7 @@ # Example configuration for intersphinx: refer to the Python standard library. -intersphinx_mapping = {'python': ('https://docs.python.org/3', None)} +intersphinx_mapping = {"python": ("https://docs.python.org/3", None)} def setup(app): diff --git a/examples/docs/configs/base_config.py b/examples/docs/configs/base_config.py index c2a76b64..ae6c2fcf 100644 --- a/examples/docs/configs/base_config.py +++ b/examples/docs/configs/base_config.py @@ -1,5 +1,5 @@ # Example config used by load_config_app.py c = get_config() # noqa -c.MyClass.name = 'Harvard' +c.MyClass.name = "Harvard" c.MyClass.ranking = 100 diff --git a/examples/docs/configs/main_config.py b/examples/docs/configs/main_config.py index 4f16865b..aad1072a 100644 --- a/examples/docs/configs/main_config.py +++ b/examples/docs/configs/main_config.py @@ -3,7 +3,7 @@ c = get_config() # noqa # Load everything from base_config.py -load_subconfig('base_config.py') # noqa +load_subconfig("base_config.py") # noqa # Now override one of the values -c.School.name = 'Caltech' +c.School.name = "Caltech" diff --git a/pyproject.toml b/pyproject.toml index acee9d00..6e944824 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -75,33 +75,28 @@ features = ["test"] test = "mypy --install-types --non-interactive {args}" [tool.hatch.envs.lint] -dependencies = ["black==23.3.0", "mdformat>0.7", "ruff==0.1.1"] +dependencies = ["mdformat>0.7", "ruff==0.1.3"] detached = true [tool.hatch.envs.lint.scripts] style = [ "ruff {args:.}", - "black --check --diff {args:.}", + "ruff format {args:.}", "mdformat --check {args:docs *.md}" ] fmt = [ - "black {args:.}", "ruff --fix {args:.}", + "ruff format {args:.}", "mdformat {args:docs *.md}" ] [tool.mypy] files = "traitlets" python_version = "3.8" -strict = false -check_untyped_defs = true -disallow_any_generics = false -disable_error_code = ["no-untyped-call"] +strict = true enable_error_code = ["ignore-without-code", "redundant-expr", "truthy-bool"] -no_implicit_reexport = false pretty = true show_error_context = true show_error_codes = true -warn_return_any = false warn_unreachable = true exclude = ["examples/docs/configs", "traitlets/tests/test_typing.py"] @@ -162,27 +157,16 @@ exclude_lines = [ relative_files = true source = ["traitlets"] -[tool.black] -line-length = 100 -skip-string-normalization = true -target-version = ["py37"] - [tool.ruff] -target-version = "py37" +target-version = "py38" line-length = 100 + +[tool.ruff.lint] select = [ - "A", "ANN", "B", "C", "E", "F", "FBT", "I", "N", "Q", "RUF", "S", "T", + "A", "B", "C", "E", "F", "FBT", "I", "N", "Q", "RUF", "S", "T", "UP", "W", "YTT", ] ignore = [ - # Dynamically typed expressions (typing.Any) are disallowed in `key` - "ANN401", - # Missing type annotation for `self` in method - "ANN101", - # Missing type annotation for `cls` in classmethod - "ANN102", - # ANN202 Missing return type annotation for private function - "ANN202", # Allow non-abstract empty methods in abstract base classes "B027", # Ignore McCabe complexity @@ -231,7 +215,7 @@ unfixable = [ "RUF100", ] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] # B011 Do not call assert False since python -O removes these calls # F841 local variable 'foo' is assigned to but never used # C408 Unnecessary `dict` call @@ -242,15 +226,13 @@ unfixable = [ # F841 Local variable `t` is assigned to but never used # B018 Found useless expression # S301 `pickle` and modules that wrap..." -"tests/*" = ["ANN", "B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", +"tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841", "B018", "S301"] # B003 Assigning to os.environ doesn't clear the environment "tests/config/*" = ["B003", "B018", "S301"] # F401 `_version.__version__` imported but unused # F403 `from .traitlets import *` used; unable to detect undefined names "traitlets/*__init__.py" = ["F401", "F403"] -"docs/*" = ["ANN"] -"examples/*" = ["ANN"] [tool.repo-review] ignore = ["PY007", "PP308", "GH102", "PC140", "MY101"] diff --git a/tests/config/test_application.py b/tests/config/test_application.py index 1a5ec71e..34c8d8c9 100644 --- a/tests/config/test_application.py +++ b/tests/config/test_application.py @@ -130,7 +130,7 @@ def test_basic(self): self.assertEqual(app.config_file, "") def test_app_name_set_via_constructor(self): - app = MyApp(name='set_via_constructor') + app = MyApp(name="set_via_constructor") assert app.name == "set_via_constructor" def test_mro_discovery(self): diff --git a/tests/config/test_argcomplete.py b/tests/config/test_argcomplete.py index 52ed6d2b..0cd992c6 100644 --- a/tests/config/test_argcomplete.py +++ b/tests/config/test_argcomplete.py @@ -133,21 +133,21 @@ def run_completer( def test_complete_simple_app(self, argcomplete_on): app = ArgcompleteApp() expected = [ - '--help', - '--debug', - '--show-config', - '--show-config-json', - '--log-level', - '--Application.', - '--ArgcompleteApp.', + "--help", + "--debug", + "--show-config", + "--show-config-json", + "--log-level", + "--Application.", + "--ArgcompleteApp.", ] assert set(self.run_completer(app, "app --")) == set(expected) # completing class traits assert set(self.run_completer(app, "app --App")) > { - '--Application.show_config', - '--Application.log_level', - '--Application.log_format', + "--Application.show_config", + "--Application.log_level", + "--Application.log_format", } def test_complete_custom_completers(self, argcomplete_on): @@ -195,9 +195,9 @@ def test_complete_subcommands_subapp1(self, argcomplete_on): app = MainApp() try: assert set(self.run_completer(app, "app subapp1 --Sub")) > { - '--SubApp1.show_config', - '--SubApp1.log_level', - '--SubApp1.log_format', + "--SubApp1.show_config", + "--SubApp1.log_level", + "--SubApp1.log_format", } finally: SubApp1.clear_instance() @@ -206,8 +206,8 @@ def test_complete_subcommands_subapp2(self, argcomplete_on): app = MainApp() try: assert set(self.run_completer(app, "app subapp2 --")) > { - '--Application.', - '--SubApp2.', + "--Application.", + "--SubApp2.", } finally: SubApp2.clear_instance() @@ -215,5 +215,5 @@ def test_complete_subcommands_subapp2(self, argcomplete_on): def test_complete_subcommands_main(self, argcomplete_on): app = MainApp() completions = set(self.run_completer(app, "app --")) - assert completions > {'--Application.', '--MainApp.'} + assert completions > {"--Application.", "--MainApp."} assert "--SubApp1." not in completions and "--SubApp2." not in completions diff --git a/tests/test_typing.py b/tests/test_typing.py index 2b4073ec..2c0cb17e 100644 --- a/tests/test_typing.py +++ b/tests/test_typing.py @@ -1,6 +1,6 @@ from __future__ import annotations -import typing +import typing as t import pytest @@ -24,9 +24,9 @@ ) from traitlets.config import Config -if not typing.TYPE_CHECKING: +if not t.TYPE_CHECKING: - def reveal_type(*args, **kwargs): + def reveal_type(*args: t.Any, **kwargs: t.Any) -> None: pass @@ -34,12 +34,12 @@ def reveal_type(*args, **kwargs): class Foo: - def __init__(self, c): + def __init__(self, c: t.Any) -> None: self.c = c @pytest.mark.mypy_testing -def mypy_decorator_typing(): +def mypy_decorator_typing() -> None: class T(HasTraits): foo = Unicode("").tag(config=True) @@ -48,11 +48,11 @@ def _default_foo(self) -> str: return "hi" @observe("foo") - def _foo_observer(self, change: typing.Any) -> bool: + def _foo_observer(self, change: t.Any) -> bool: return True @validate("foo") - def _foo_validate(self, commit: typing.Any) -> bool: + def _foo_validate(self, commit: t.Any) -> bool: return True t = T() @@ -62,7 +62,7 @@ def _foo_validate(self, commit: typing.Any) -> bool: @pytest.mark.mypy_testing -def mypy_config_typing(): +def mypy_config_typing() -> None: c = Config( { "ExtractOutputPreprocessor": {"enabled": True}, @@ -72,7 +72,7 @@ def mypy_config_typing(): @pytest.mark.mypy_testing -def mypy_union_typing(): +def mypy_union_typing() -> None: class T(HasTraits): style = Union( [Unicode("default"), Type(klass=object)], @@ -90,7 +90,7 @@ class T(HasTraits): @pytest.mark.mypy_testing -def mypy_list_typing(): +def mypy_list_typing() -> None: class T(HasTraits): latex_command = List( ["xelatex", "{filename}", "-quiet"], help="Shell command used to compile latex." @@ -106,7 +106,7 @@ class T(HasTraits): @pytest.mark.mypy_testing -def mypy_dict_typing(): +def mypy_dict_typing() -> None: class T(HasTraits): foo = Dict({}, help="Shell command used to compile latex.").tag(config=True) @@ -120,7 +120,7 @@ class T(HasTraits): @pytest.mark.mypy_testing -def mypy_type_typing(): +def mypy_type_typing() -> None: class KernelSpec: item = Unicode("foo") @@ -145,7 +145,7 @@ class KernelSpecManager(HasTraits): @pytest.mark.mypy_testing -def mypy_unicode_typing(): +def mypy_unicode_typing() -> None: class T(HasTraits): export_format = Unicode( allow_none=False, @@ -163,9 +163,7 @@ class T(HasTraits): reveal_type( Unicode( # R: traitlets.traitlets.Unicode[builtins.str, Union[builtins.str, builtins.bytes]] "" - ).tag( - sync=True - ) + ).tag(sync=True) ) reveal_type( Unicode( # R: traitlets.traitlets.Unicode[Union[builtins.str, None], Union[builtins.str, builtins.bytes, None]] @@ -175,9 +173,7 @@ class T(HasTraits): reveal_type( Unicode( # R: traitlets.traitlets.Unicode[Union[builtins.str, None], Union[builtins.str, builtins.bytes, None]] None, allow_none=True - ).tag( - sync=True - ) + ).tag(sync=True) ) reveal_type( T.export_format # R: traitlets.traitlets.Unicode[builtins.str, Union[builtins.str, builtins.bytes]] @@ -186,7 +182,7 @@ class T(HasTraits): @pytest.mark.mypy_testing -def mypy_set_typing(): +def mypy_set_typing() -> None: class T(HasTraits): remove_cell_tags = Set( Unicode(), @@ -222,7 +218,7 @@ class T(HasTraits): @pytest.mark.mypy_testing -def mypy_any_typing(): +def mypy_any_typing() -> None: class T(HasTraits): attributes = Any( config=True, @@ -244,7 +240,7 @@ class T(HasTraits): @pytest.mark.mypy_testing -def mypy_bool_typing(): +def mypy_bool_typing() -> None: class T(HasTraits): b = Bool(True).tag(sync=True) ob = Bool(None, allow_none=True).tag(sync=True) @@ -266,9 +262,7 @@ class T(HasTraits): reveal_type( Bool( # R: traitlets.traitlets.Bool[Union[builtins.bool, None], Union[builtins.bool, builtins.int, None]] None, allow_none=True - ).tag( - sync=True - ) + ).tag(sync=True) ) reveal_type( T.b # R: traitlets.traitlets.Bool[builtins.bool, Union[builtins.bool, builtins.int]] @@ -287,7 +281,7 @@ class T(HasTraits): @pytest.mark.mypy_testing -def mypy_int_typing(): +def mypy_int_typing() -> None: class T(HasTraits): i: Int[int, int] = Int(42).tag(sync=True) oi: Int[int | None, int | None] = Int(42, allow_none=True).tag(sync=True) @@ -318,7 +312,7 @@ class T(HasTraits): @pytest.mark.mypy_testing -def mypy_cint_typing(): +def mypy_cint_typing() -> None: class T(HasTraits): i = CInt(42).tag(sync=True) oi = CInt(42, allow_none=True).tag(sync=True) @@ -342,7 +336,7 @@ class T(HasTraits): @pytest.mark.mypy_testing -def mypy_tcp_typing(): +def mypy_tcp_typing() -> None: class T(HasTraits): tcp = TCPAddress() otcp = TCPAddress(None, allow_none=True) @@ -372,7 +366,7 @@ class T(HasTraits): @pytest.mark.mypy_testing -def mypy_instance_typing(): +def mypy_instance_typing() -> None: class T(HasTraits): inst = Instance(Foo) oinst = Instance(Foo, allow_none=True) diff --git a/traitlets/__init__.py b/traitlets/__init__.py index 2641c443..70ed818a 100644 --- a/traitlets/__init__.py +++ b/traitlets/__init__.py @@ -20,7 +20,7 @@ ] -class Sentinel(traitlets.Sentinel): # type:ignore[name-defined] +class Sentinel(traitlets.Sentinel): # type:ignore[name-defined, misc] def __init__(self, *args: _t.Any, **kwargs: _t.Any) -> None: super().__init__(*args, **kwargs) warn( diff --git a/traitlets/config/argcomplete_config.py b/traitlets/config/argcomplete_config.py index 9588b889..0d63e75f 100644 --- a/traitlets/config/argcomplete_config.py +++ b/traitlets/config/argcomplete_config.py @@ -10,7 +10,7 @@ try: import argcomplete - from argcomplete import CompletionFinder + from argcomplete import CompletionFinder # type:ignore[attr-defined] except ImportError: # This module and its utility methods are written to not crash even # if argcomplete is not installed. @@ -45,7 +45,7 @@ def get_argcomplete_cwords() -> t.Optional[t.List[str]]: cword_suffix, comp_words, last_wordbreak_pos, - ) = argcomplete.split_line(comp_line, comp_point) + ) = argcomplete.split_line(comp_line, comp_point) # type:ignore[attr-defined] except ModuleNotFoundError: return None @@ -73,7 +73,7 @@ def increment_argcomplete_index() -> None: os.environ["_ARGCOMPLETE"] = str(int(os.environ["_ARGCOMPLETE"]) + 1) except Exception: try: - argcomplete.debug("Unable to increment $_ARGCOMPLETE", os.environ["_ARGCOMPLETE"]) + argcomplete.debug("Unable to increment $_ARGCOMPLETE", os.environ["_ARGCOMPLETE"]) # type:ignore[attr-defined] except (KeyError, ModuleNotFoundError): pass @@ -188,7 +188,7 @@ def _get_completions( break completions: t.List[str] - completions = super()._get_completions(comp_words, cword_prefix, *args) + completions = super()._get_completions(comp_words, cword_prefix, *args) # type:ignore[no-untyped-call] # For subcommand-handling: it is difficult to get this to work # using argparse subparsers, because the ArgumentParser accepts @@ -196,7 +196,7 @@ def _get_completions( # Instead, check if comp_words only consists of the script, # if so check if any subcommands start with cword_prefix. if self.subcommands and len(comp_words) == 1: - argcomplete.debug("Adding subcommands for", cword_prefix) + argcomplete.debug("Adding subcommands for", cword_prefix) # type:ignore[attr-defined] completions.extend(subc for subc in self.subcommands if subc.startswith(cword_prefix)) return completions @@ -206,7 +206,7 @@ def _get_option_completions( ) -> t.List[str]: """Overridden to add --Class. completions when appropriate""" completions: t.List[str] - completions = super()._get_option_completions(parser, cword_prefix) + completions = super()._get_option_completions(parser, cword_prefix) # type:ignore[no-untyped-call] if cword_prefix.endswith("."): return completions diff --git a/traitlets/config/configurable.py b/traitlets/config/configurable.py index 776138f4..3e8c868c 100644 --- a/traitlets/config/configurable.py +++ b/traitlets/config/configurable.py @@ -164,7 +164,7 @@ def _load_config( self, cfg: Config, section_names: list[str] | None = None, - traits: dict[str, TraitType] | None = None, + traits: dict[str, TraitType[t.Any, t.Any]] | None = None, ) -> None: """load traits from a Config object""" @@ -481,7 +481,7 @@ def _validate_log(self, proposal: Bunch) -> LoggerType: UserWarning, stacklevel=2, ) - return proposal.value # type:ignore[no-any-return] + return t.cast(LoggerType, proposal.value) @default("log") def _log_default(self) -> LoggerType: @@ -502,14 +502,16 @@ def _get_log_handler(self) -> logging.Handler | None: """ if not self.log: return None - logger = self.log if isinstance(self.log, logging.Logger) else self.log.logger + logger: logging.Logger = ( + self.log if isinstance(self.log, logging.Logger) else self.log.logger + ) if not getattr(logger, "handlers", None): # no handlers attribute or empty handlers list return None - return logger.handlers[0] # type:ignore[no-any-return] + return logger.handlers[0] -CT = t.TypeVar('CT', bound='SingletonConfigurable') +CT = t.TypeVar("CT", bound="SingletonConfigurable") class SingletonConfigurable(LoggingConfigurable): diff --git a/traitlets/config/loader.py b/traitlets/config/loader.py index dc0add57..4451d680 100644 --- a/traitlets/config/loader.py +++ b/traitlets/config/loader.py @@ -2,7 +2,6 @@ # Copyright (c) IPython Development Team. # Distributed under the terms of the Modified BSD License. -# ruff: noqa: ANN201, ANN001, ANN204, ANN102, ANN003, ANN206, ANN002 from __future__ import annotations import argparse @@ -13,8 +12,9 @@ import re import sys import typing as t +from logging import Logger -from traitlets.traitlets import Any, Container, Dict, HasTraits, List, Undefined +from traitlets.traitlets import Any, Container, Dict, HasTraits, List, TraitType, Undefined from ..utils import cast_unicode, filefind, warnings @@ -64,7 +64,7 @@ def __str__(self) -> str: class ArgumentParser(argparse.ArgumentParser): """Simple argparse subclass that prints help to stdout by default.""" - def print_help(self, file=None): + def print_help(self, file: t.Any = None) -> None: if file is None: file = sys.stdout return super().print_help(file) @@ -77,7 +77,7 @@ def print_help(self, file=None): # ----------------------------------------------------------------------------- -def execfile(fname, glob): +def execfile(fname: str, glob: dict[str, Any]) -> None: with open(fname, "rb") as f: exec(compile(f.read(), fname, "exec"), glob, glob) # noqa @@ -102,19 +102,19 @@ class LazyConfigValue(HasTraits): _prepend: List = List() _inserts: List = List() - def append(self, obj): + def append(self, obj: t.Any) -> None: """Append an item to a List""" self._extend.append(obj) - def extend(self, other): + def extend(self, other: t.Any) -> None: """Extend a list""" self._extend.extend(other) - def prepend(self, other): + def prepend(self, other: t.Any) -> None: """like list.extend, but for the front""" self._prepend[:0] = other - def merge_into(self, other): + def merge_into(self, other: t.Any) -> t.Any: """ Merge with another earlier LazyConfigValue or an earlier container. This is useful when having global system-wide configuration files. @@ -147,7 +147,7 @@ def merge_into(self, other): # other is a container, reify now. return self.get_value(other) - def insert(self, index, other): + def insert(self, index: int, other: t.Any) -> None: if not isinstance(index, int): raise TypeError("An integer is required") self._inserts.append((index, other)) @@ -156,7 +156,7 @@ def insert(self, index, other): # update is used for both dict and set _update = Any() - def update(self, other): + def update(self, other: t.Any) -> None: """Update either a set or dict""" if self._update is None: if isinstance(other, dict): @@ -166,11 +166,11 @@ def update(self, other): self._update.update(other) # set methods - def add(self, obj): + def add(self, obj: t.Any) -> None: """Add an item to a set""" self.update({obj}) - def get_value(self, initial): + def get_value(self, initial: t.Any) -> t.Any: """construct the value from the initial one after applying any insert / extend / update changes @@ -193,7 +193,7 @@ def get_value(self, initial): self._value = value return value - def to_dict(self): + def to_dict(self) -> dict[str, t.Any]: """return JSONable dict form of my data Currently update as dict or set, extend, prepend as lists, and inserts as list of tuples. @@ -216,7 +216,7 @@ def __repr__(self) -> str: return f"<{self.__class__.__name__} {self.to_dict()!r}>" -def _is_section_key(key): +def _is_section_key(key: str) -> bool: """Is a Config key a section name (does it start with a capital)?""" if key and key[0].upper() == key[0] and not key.startswith("_"): return True @@ -242,7 +242,7 @@ def __init__(self, *args: t.Any, **kwds: t.Any) -> None: dict.__init__(self, *args, **kwds) self._ensure_subconfig() - def _ensure_subconfig(self): + def _ensure_subconfig(self) -> None: """ensure that sub-dicts that should be Config objects are casts dicts that are under section keys to Config objects, @@ -253,11 +253,11 @@ def _ensure_subconfig(self): if _is_section_key(key) and isinstance(obj, dict) and not isinstance(obj, Config): setattr(self, key, Config(obj)) - def _merge(self, other): + def _merge(self, other: t.Any) -> None: """deprecated alias, use Config.merge()""" self.merge(other) - def merge(self, other): + def merge(self, other: t.Any) -> None: """merge another config object into this one""" to_update = {} for k, v in other.items(): @@ -308,16 +308,16 @@ def __contains__(self, key: t.Any) -> bool: # .has_key is deprecated for dictionaries. has_key = __contains__ - def _has_section(self, key): + def _has_section(self, key: str) -> bool: return _is_section_key(key) and key in self - def copy(self): + def copy(self) -> dict[str, t.Any]: return type(self)(dict.copy(self)) - def __copy__(self): + def __copy__(self) -> dict[str, t.Any]: return self.copy() - def __deepcopy__(self, memo): + def __deepcopy__(self, memo: t.Any) -> Config: new_config = type(self)() for key, value in self.items(): if isinstance(value, (Config, LazyConfigValue)): @@ -329,7 +329,7 @@ def __deepcopy__(self, memo): new_config[key] = value return new_config - def __getitem__(self, key): + def __getitem__(self, key: str) -> t.Any: try: return dict.__getitem__(self, key) except KeyError: @@ -354,7 +354,7 @@ def __setitem__(self, key: str, value: t.Any) -> None: ) dict.__setitem__(self, key, value) - def __getattr__(self, key): + def __getattr__(self, key: str) -> t.Any: if key.startswith("__"): return dict.__getattr__(self, key) # type:ignore[attr-defined] try: @@ -384,10 +384,10 @@ class DeferredConfig: pass - def get_value(self, trait): + def get_value(self, trait: TraitType[t.Any, t.Any]) -> t.Any: raise NotImplementedError("Implement in subclasses") - def _super_repr(self): + def _super_repr(self) -> str: # explicitly call super on direct parent return super(self.__class__, self).__repr__() @@ -410,7 +410,7 @@ class DeferredConfigString(str, DeferredConfig): .. versionadded:: 5.0 """ - def get_value(self, trait): + def get_value(self, trait: TraitType[t.Any, t.Any]) -> t.Any: """Get the value stored in this string""" s = str(self) try: @@ -425,7 +425,7 @@ def __repr__(self) -> str: return f"{self.__class__.__name__}({self._super_repr()})" -class DeferredConfigList(list, DeferredConfig): # type:ignore[type-arg] +class DeferredConfigList(t.List[t.Any], DeferredConfig): """Config value for loading config from a list of strings Interpretation is deferred until it is loaded into the trait. @@ -441,7 +441,7 @@ class DeferredConfigList(list, DeferredConfig): # type:ignore[type-arg] .. versionadded:: 5.0 """ - def get_value(self, trait): + def get_value(self, trait: TraitType[t.Any, t.Any]) -> t.Any: """Get the value stored in this string""" if hasattr(trait, "from_string_list"): src = list(self) @@ -487,12 +487,12 @@ class ConfigLoader: handled elsewhere. """ - def _log_default(self): + def _log_default(self) -> Logger: from traitlets.log import get_logger - return get_logger() + return t.cast(Logger, get_logger()) - def __init__(self, log: t.Any = None) -> None: + def __init__(self, log: Logger | None = None) -> None: """A base class for config loaders. log : instance of :class:`logging.Logger` to use. @@ -513,10 +513,10 @@ def __init__(self, log: t.Any = None) -> None: else: self.log = log - def clear(self): + def clear(self) -> None: self.config = Config() - def load_config(self): + def load_config(self) -> Config: """Load a config from somewhere, return a :class:`Config` instance. Usually, this will cause self.config to be set and then returned. @@ -550,7 +550,7 @@ def __init__(self, filename: str, path: str | None = None, **kw: t.Any) -> None: self.path = path self.full_filename = "" - def _find_file(self): + def _find_file(self) -> None: """Try to find the file by searching the paths.""" self.full_filename = filefind(self.filename, self.path) @@ -567,7 +567,7 @@ class JSONFileConfigLoader(FileConfigLoader): """ - def load_config(self): + def load_config(self) -> Config: """Load the config from a file and return it as a Config object.""" self.clear() try: @@ -578,11 +578,11 @@ def load_config(self): self.config = self._convert_to_config(dct) return self.config - def _read_file_as_dict(self): + def _read_file_as_dict(self) -> dict[str, t.Any]: with open(self.full_filename) as f: - return json.load(f) + return t.cast("dict[str, t.Any]", json.load(f)) - def _convert_to_config(self, dictionary): + def _convert_to_config(self, dictionary: dict[str, t.Any]) -> Config: if "version" in dictionary: version = dictionary.pop("version") else: @@ -593,11 +593,11 @@ def _convert_to_config(self, dictionary): else: raise ValueError(f"Unknown version of JSON config file: {version}") - def __enter__(self): + def __enter__(self) -> Config: self.load_config() return self.config - def __exit__(self, exc_type, exc_value, traceback): + def __exit__(self, exc_type: t.Any, exc_value: t.Any, traceback: t.Any) -> None: """ Exit the context manager but do not handle any errors. @@ -617,7 +617,7 @@ class PyFileConfigLoader(FileConfigLoader): path, then executing it to construct a Config object. """ - def load_config(self): + def load_config(self) -> Config: """Load the config from a file and return it as a Config object.""" self.clear() try: @@ -627,7 +627,7 @@ def load_config(self): self._read_file_as_dict() return self.config - def load_subconfig(self, fname, path=None): + def load_subconfig(self, fname: str, path: str | None = None) -> None: """Injected into config file namespace as load_subconfig""" if path is None: path = self.path @@ -642,10 +642,10 @@ def load_subconfig(self, fname, path=None): else: self.config.merge(sub_config) - def _read_file_as_dict(self): + def _read_file_as_dict(self) -> None: """Load the config file into self.config, with recursive loading.""" - def get_config(): + def get_config() -> Config: """Unnecessary now, but a deprecation warning is more trouble than it's worth.""" return self.config @@ -667,7 +667,9 @@ class CommandLineConfigLoader(ConfigLoader): here. """ - def _exec_config_str(self, lhs, rhs, trait=None): + def _exec_config_str( + self, lhs: t.Any, rhs: t.Any, trait: TraitType[t.Any, t.Any] | None = None + ) -> None: """execute self.config. = * expands ~ with expanduser @@ -694,7 +696,7 @@ def _exec_config_str(self, lhs, rhs, trait=None): section[key] = value return - def _load_flag(self, cfg): + def _load_flag(self, cfg: t.Any) -> None: """update self.config from a flag, which can be a dict or Config""" if isinstance(cfg, (dict, Config)): # don't clobber whole config sections, update @@ -723,7 +725,13 @@ class _KVAction(argparse.Action): Always """ - def __call__(self, parser, namespace, values, option_string=None): + def __call__( # type:ignore[override] + self, + parser: argparse.ArgumentParser, + namespace: dict[str, t.Any], + values: t.Sequence[t.Any], + option_string: str | None = None, + ) -> None: if isinstance(values, str): values = [values] values = ["-" if v is _DASH_REPLACEMENT else v for v in values] @@ -742,7 +750,7 @@ class _DefaultOptionDict(dict): # type:ignore[type-arg] but acts as if all --Class.trait options are predefined """ - def _add_kv_action(self, key): + def _add_kv_action(self, key: str) -> None: self[key] = _KVAction( option_strings=[key], dest=key.lstrip("-").replace(".", _DOT_REPLACEMENT), @@ -761,13 +769,13 @@ def __contains__(self, key: t.Any) -> bool: return True return False - def __getitem__(self, key): + def __getitem__(self, key: str) -> t.Any: if key in self: return super().__getitem__(key) else: raise KeyError(key) - def get(self, key, default=None): + def get(self, key: str, default: t.Any = None) -> t.Any: try: return self[key] except KeyError: @@ -777,7 +785,9 @@ def get(self, key, default=None): class _KVArgParser(argparse.ArgumentParser): """subclass of ArgumentParser where any --Class.trait option is implicitly defined""" - def parse_known_args(self, args=None, namespace=None): + def parse_known_args( # type:ignore[override] + self, args: t.Sequence[str] | None = None, namespace: argparse.Namespace | None = None + ) -> tuple[argparse.Namespace | None, list[str]]: # must be done immediately prior to parsing because if we do it in init, # registration of explicit actions via parser.add_option will fail during setup for container in (self, self._optionals): @@ -850,7 +860,13 @@ def __init__( kwargs.update(parser_kw) self.parser_kw = kwargs - def load_config(self, argv=None, aliases=None, flags=_deprecated, classes=None): + def load_config( + self, + argv: list[str] | None = None, + aliases: t.Any = None, + flags: t.Any = _deprecated, + classes: t.Any = None, + ) -> Config: """Parse command line arguments and return as a Config object. Parameters @@ -885,26 +901,27 @@ def load_config(self, argv=None, aliases=None, flags=_deprecated, classes=None): self._convert_to_config() return self.config - def get_extra_args(self): + def get_extra_args(self) -> list[str]: if hasattr(self, "extra_args"): return self.extra_args else: return [] - def _create_parser(self): + def _create_parser(self) -> None: self.parser = self.parser_class( - *self.parser_args, **self.parser_kw # type:ignore[arg-type] + *self.parser_args, + **self.parser_kw, # type:ignore[arg-type] ) self._add_arguments(self.aliases, self.flags, self.classes) - def _add_arguments(self, aliases, flags, classes): + def _add_arguments(self, aliases: t.Any, flags: t.Any, classes: t.Any) -> None: raise NotImplementedError("subclasses must implement _add_arguments") def _argcomplete(self, classes: list[t.Any], subcommands: SubcommandsDict | None) -> None: """If argcomplete is enabled, allow triggering command-line autocompletion""" pass - def _parse_args(self, args): + def _parse_args(self, args: t.Any) -> t.Any: """self.parser->self.parsed_data""" uargs = [cast_unicode(a) for a in args] @@ -921,7 +938,7 @@ def _parse_args(self, args): unpacked_aliases["-" + al] = "--" + alias_target unpacked_aliases["--" + al] = "--" + alias_target - def _replace(arg): + def _replace(arg: str) -> str: if arg == "-": return _DASH_REPLACEMENT for k, v in unpacked_aliases.items(): @@ -943,7 +960,7 @@ def _replace(arg): self.parsed_data = self.parser.parse_args(to_parse) self.extra_args = extra_args - def _convert_to_config(self): + def _convert_to_config(self) -> None: """self.parsed_data->self.config""" for k, v in vars(self.parsed_data).items(): *path, key = k.split(".") @@ -964,7 +981,9 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: kwargs["nargs"] = 0 super().__init__(*args, **kwargs) - def __call__(self, parser, namespace, values, option_string=None): + def __call__( + self, parser: t.Any, namespace: t.Any, values: t.Any, option_string: str | None = None + ) -> None: if self.nargs == 0 or values is Undefined: if not hasattr(namespace, "_flags"): namespace._flags = [] @@ -981,9 +1000,10 @@ class KVArgParseConfigLoader(ArgParseConfigLoader): parser_class = _KVArgParser # type:ignore[assignment] - def _add_arguments(self, aliases, flags, classes): + def _add_arguments(self, aliases: t.Any, flags: t.Any, classes: t.Any) -> None: alias_flags: dict[str, t.Any] = {} argparse_kwds: dict[str, t.Any] + argparse_traits: dict[str, t.Any] paa = self.parser.add_argument self.parser.set_defaults(_flags=[]) paa("extra_args", nargs="*") @@ -1061,7 +1081,7 @@ def _add_arguments(self, aliases, flags, classes): argcompleter, key=key ) - def _convert_to_config(self): + def _convert_to_config(self) -> None: """self.parsed_data->self.config, parse unrecognized extra args via KVLoader.""" extra_args = self.extra_args @@ -1118,7 +1138,7 @@ def _argcomplete(self, classes: list[t.Any], subcommands: SubcommandsDict | None from . import argcomplete_config - finder = argcomplete_config.ExtendedCompletionFinder() + finder = argcomplete_config.ExtendedCompletionFinder() # type:ignore[no-untyped-call] finder.config_classes = classes finder.subcommands = list(subcommands or []) # for ease of testing, pass through self._argcomplete_kwargs if set @@ -1141,7 +1161,7 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: super().__init__(*args, **kwargs) -def load_pyconfig_files(config_files, path): +def load_pyconfig_files(config_files: list[str], path: str) -> Config: """Load multiple Python config files, merging each of them in turn. Parameters diff --git a/traitlets/config/sphinxdoc.py b/traitlets/config/sphinxdoc.py index 300c0a0b..635b6bdf 100644 --- a/traitlets/config/sphinxdoc.py +++ b/traitlets/config/sphinxdoc.py @@ -32,15 +32,18 @@ Cross reference like this: :configtrait:`Application.log_datefmt`. """ -# ruff: noqa: ANN201, ANN001, ANN204, ANN102, ANN003, ANN206, ANN002 +from __future__ import annotations + +import typing as t from collections import defaultdict from textwrap import dedent -from traitlets import Undefined +from traitlets import HasTraits, Undefined +from traitlets.config.application import Application from traitlets.utils.text import indent -def setup(app): +def setup(app: t.Any) -> dict[str, t.Any]: """Registers the Sphinx extension. You shouldn't need to call this directly; configure Sphinx to use this @@ -51,7 +54,7 @@ def setup(app): return metadata -def interesting_default_value(dv): +def interesting_default_value(dv: t.Any) -> bool: if (dv is None) or (dv is Undefined): return False if isinstance(dv, (str, list, tuple, dict, set)): @@ -59,7 +62,7 @@ def interesting_default_value(dv): return True -def format_aliases(aliases): +def format_aliases(aliases: list[str]) -> str: fmted = [] for a in aliases: dashes = "-" if len(a) == 1 else "--" @@ -67,7 +70,7 @@ def format_aliases(aliases): return ", ".join(fmted) -def class_config_rst_doc(cls, trait_aliases): +def class_config_rst_doc(cls: type[HasTraits], trait_aliases: dict[str, t.Any]) -> str: """Generate rST documentation for this class' config options. Excludes traits defined on parent classes. @@ -77,7 +80,7 @@ def class_config_rst_doc(cls, trait_aliases): for _, trait in sorted(cls.class_traits(config=True).items()): ttype = trait.__class__.__name__ - fullname = classname + "." + trait.name + fullname = classname + "." + (trait.name or "") lines += [".. configtrait:: " + fullname, ""] help = trait.help.rstrip() or "No description" @@ -86,7 +89,7 @@ def class_config_rst_doc(cls, trait_aliases): # Choices or type if "Enum" in ttype: # include Enum choices - lines.append(indent(":options: " + ", ".join("``%r``" % x for x in trait.values))) + lines.append(indent(":options: " + ", ".join("``%r``" % x for x in trait.values))) # type:ignore[attr-defined] else: lines.append(indent(":trait type: " + ttype)) @@ -115,7 +118,7 @@ def class_config_rst_doc(cls, trait_aliases): return "\n".join(lines) -def reverse_aliases(app): +def reverse_aliases(app: Application) -> dict[str, list[str]]: """Produce a mapping of trait names to lists of command line aliases.""" res = defaultdict(list) for alias, trait in app.aliases.items(): @@ -135,7 +138,7 @@ def reverse_aliases(app): return res -def write_doc(path, title, app, preamble=None): +def write_doc(path: str, title: str, app: Application, preamble: str | None = None) -> None: """Write a rst file documenting config options for a traitlets application. Parameters diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index e4df8fae..31ad52bb 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -39,8 +39,6 @@ # Adapted from enthought.traits, Copyright (c) Enthought, Inc., # also under the terms of the Modified BSD License. -# ruff: noqa: ANN001, ANN204, ANN201, ANN003, ANN206, ANN002 - from __future__ import annotations import contextlib @@ -166,11 +164,11 @@ class TraitError(Exception): # ----------------------------------------------------------------------------- -def isidentifier(s): - return s.isidentifier() +def isidentifier(s: t.Any) -> bool: + return t.cast(bool, s.isidentifier()) -def _safe_literal_eval(s): +def _safe_literal_eval(s: str) -> t.Any: """Safely evaluate an expression Returns original string if eval fails. @@ -183,7 +181,7 @@ def _safe_literal_eval(s): return s -def is_trait(t): +def is_trait(t: t.Any) -> bool: """Returns whether the given value is an instance or subclass of TraitType.""" return isinstance(t, TraitType) or (isinstance(t, type) and issubclass(t, TraitType)) @@ -229,7 +227,7 @@ def __str__(self) -> str: return self.__repr__() -def getmembers(object, predicate=None): +def getmembers(object: t.Any, predicate: t.Any = None) -> list[tuple[str, t.Any]]: """A safe version of inspect.getmembers that handles missing attributes. This is useful when there are descriptor based attributes that for @@ -249,7 +247,7 @@ def getmembers(object, predicate=None): return results -def _validate_link(*tuples): +def _validate_link(*tuples: t.Any) -> None: """Validate arguments for traitlet link functions""" for tup in tuples: if not len(tup) == 2: @@ -310,14 +308,14 @@ def link(self) -> None: self.target[0].observe(self._update_source, names=self.target[1]) @contextlib.contextmanager - def _busy_updating(self): + def _busy_updating(self) -> t.Any: self.updating = True try: yield finally: self.updating = False - def _update_target(self, change): + def _update_target(self, change: t.Any) -> None: if self.updating: return with self._busy_updating(): @@ -327,7 +325,7 @@ def _update_target(self, change): f"Broken link {self}: the source value changed while updating " "the target." ) - def _update_source(self, change): + def _update_source(self, change: t.Any) -> None: if self.updating: return with self._busy_updating(): @@ -392,14 +390,14 @@ def link(self) -> None: self.source[0].observe(self._update, names=self.source[1]) @contextlib.contextmanager - def _busy_updating(self): + def _busy_updating(self) -> t.Any: self.updating = True try: yield finally: self.updating = False - def _update(self, change): + def _update(self, change: t.Any) -> None: if self.updating: return with self._busy_updating(): @@ -592,12 +590,12 @@ def default(self, obj: t.Any = None) -> G | None: in the same way that dynamic defaults defined by ``@default`` are. """ if self.default_value is not Undefined: - return self.default_value + return t.cast(G, self.default_value) elif hasattr(self, "make_dynamic_default"): - return self.make_dynamic_default() + return t.cast(G, self.make_dynamic_default()) else: # Undefined will raise in TraitType.get - return self.default_value + return t.cast(G, self.default_value) def get_default_value(self) -> G | None: """DEPRECATED: Retrieve the static default value for this trait. @@ -608,9 +606,9 @@ def get_default_value(self) -> G | None: DeprecationWarning, stacklevel=2, ) - return self.default_value + return t.cast(G, self.default_value) - def init_default_value(self, obj: t.Any) -> G: + def init_default_value(self, obj: t.Any) -> G | None: """DEPRECATED: Set the static default value for the trait type.""" warn( "init_default_value is deprecated in traitlets 4.0, and may be removed in the future", @@ -621,10 +619,10 @@ def init_default_value(self, obj: t.Any) -> G: obj._trait_values[self.name] = value return value - def get(self, obj: HasTraits, cls: t.Any = None) -> G | None: + def get(self, obj: HasTraits, cls: type[t.Any] | None = None) -> G | None: assert self.name is not None try: - value = obj._trait_values[self.name] # type: ignore[index] + value = obj._trait_values[self.name] except KeyError: # Check for a dynamic initializer. default = obj.trait_defaults(self.name) @@ -644,7 +642,7 @@ def get(self, obj: HasTraits, cls: t.Any = None) -> G | None: value = self._validate(obj, default) finally: obj._cross_validation_lock = _cross_validation_lock - obj._trait_values[self.name] = value # type: ignore[index] + obj._trait_values[self.name] = value obj._notify_observers( Bunch( name=self.name, @@ -653,12 +651,12 @@ def get(self, obj: HasTraits, cls: t.Any = None) -> G | None: type="default", ) ) - return value + return t.cast(G, value) except Exception as e: # This should never be reached. raise TraitError("Unexpected error in TraitType: default value not set properly") from e else: - return value + return t.cast(G, value) if t.TYPE_CHECKING: # This gives ok type information, but not specific enough (e.g. it will) @@ -749,16 +747,16 @@ def __set__(self, obj: HasTraits, value: S) -> None: else: self.set(obj, value) - def _validate(self, obj, value): + def _validate(self, obj: t.Any, value: t.Any) -> G | None: if value is None and self.allow_none: return value if hasattr(self, "validate"): value = self.validate(obj, value) if obj._cross_validation_lock is False: value = self._cross_validate(obj, value) - return value + return t.cast(G, value) - def _cross_validate(self, obj, value): + def _cross_validate(self, obj: t.Any, value: t.Any) -> G | None: if self.name in obj._trait_validators: proposal = Bunch({"trait": self, "value": value, "owner": obj}) value = obj._trait_validators[self.name](obj, proposal) @@ -772,18 +770,24 @@ def _cross_validate(self, obj, value): "use @validate decorator instead.", ) value = cross_validate(value, self) - return value + return t.cast(G, value) - def __or__(self, other): + def __or__(self, other: TraitType[t.Any, t.Any]) -> Union: if isinstance(other, Union): return Union([self, *other.trait_types]) else: return Union([self, other]) - def info(self): + def info(self) -> str: return self.info_text - def error(self, obj, value, error=None, info=None): + def error( + self, + obj: HasTraits | None, + value: t.Any, + error: Exception | None = None, + info: str | None = None, + ) -> t.NoReturn: """Raise a TraitError Parameters @@ -828,8 +832,7 @@ def error(self, obj, value, error=None, info=None): ) else: error.args = ( - "The '{}' trait contains {} which " - "expected {}, not {}.".format( + "The '{}' trait contains {} which " "expected {}, not {}.".format( self.name, chain, error.args[1], @@ -859,7 +862,7 @@ def error(self, obj, value, error=None, info=None): ) raise TraitError(e) - def get_metadata(self, key, default=None): + def get_metadata(self, key: str, default: t.Any = None) -> t.Any: """DEPRECATED: Get a metadata value. Use .metadata[key] or .metadata.get(key, default) instead. @@ -871,7 +874,7 @@ def get_metadata(self, key, default=None): warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2) return self.metadata.get(key, default) - def set_metadata(self, key, value): + def set_metadata(self, key: str, value: t.Any) -> None: """DEPRECATED: Set a metadata key/value. Use .metadata[key] = value instead. @@ -908,7 +911,7 @@ def tag(self, **metadata: t.Any) -> Self: self.metadata.update(metadata) return self - def default_value_repr(self): + def default_value_repr(self) -> str: return repr(self.default_value) @@ -932,14 +935,14 @@ def __init__(self, cb: t.Any) -> None: if self.nargs > 4: raise TraitError("a trait changed callback must have 0-4 arguments.") - def __eq__(self, other): + def __eq__(self, other: t.Any) -> bool: # The wrapper is equal to the wrapped element if isinstance(other, _CallbackWrapper): - return self.cb == other.cb + return bool(self.cb == other.cb) else: - return self.cb == other + return bool(self.cb == other) - def __call__(self, change): + def __call__(self, change: Bunch) -> None: # The wrapper is callable if self.nargs == 0: self.cb() @@ -953,7 +956,7 @@ def __call__(self, change): self.cb(change.name, change.old, change.new, change.owner) -def _callback_wrapper(cb): +def _callback_wrapper(cb: t.Any) -> _CallbackWrapper: if isinstance(cb, _CallbackWrapper): return cb else: @@ -967,7 +970,13 @@ class MetaHasDescriptors(type): instantiated and sets their name attribute. """ - def __new__(mcls, name, bases, classdict, **kwds): # noqa + def __new__( + mcls: type[MetaHasDescriptors], # noqa: N804 + name: str, + bases: tuple[type, ...], + classdict: dict[str, t.Any], + **kwds: t.Any, + ) -> MetaHasDescriptors: """Create the HasDescriptors class.""" for k, v in classdict.items(): # ---------------------------------------------------------------- @@ -985,12 +994,14 @@ def __new__(mcls, name, bases, classdict, **kwds): # noqa return super().__new__(mcls, name, bases, classdict, **kwds) - def __init__(cls, name: str, bases: t.Any, classdict: t.Any, **kwds) -> None: + def __init__( + cls, name: str, bases: tuple[type, ...], classdict: dict[str, t.Any], **kwds: t.Any + ) -> None: """Finish initializing the HasDescriptors class.""" super().__init__(name, bases, classdict, **kwds) cls.setup_class(classdict) - def setup_class(cls, classdict): + def setup_class(cls: MetaHasDescriptors, classdict: dict[str, t.Any]) -> None: """Setup descriptor instance on the class This sets the :attr:`this_class` and :attr:`name` attributes of each @@ -998,7 +1009,7 @@ def setup_class(cls, classdict): calling their :attr:`class_init` method. """ cls._descriptors = [] - cls._instance_inits = [] + cls._instance_inits: list[t.Any] = [] for k, v in classdict.items(): if isinstance(v, BaseDescriptor): v.class_init(cls, k) # type:ignore[arg-type] @@ -1012,9 +1023,9 @@ def setup_class(cls, classdict): class MetaHasTraits(MetaHasDescriptors): """A metaclass for HasTraits.""" - def setup_class(cls, classdict): # noqa + def setup_class(cls: MetaHasTraits, classdict: dict[str, t.Any]) -> None: # noqa # for only the current class - cls._trait_default_generators = {} + cls._trait_default_generators: dict[str, t.Any] = {} # also looking at base classes cls._all_trait_default_generators = {} cls._traits = {} @@ -1263,26 +1274,26 @@ def __call__(self, *args: t.Any, **kwargs: t.Any) -> t.Any: else: return self._init_call(*args, **kwargs) - def __get__(self, inst, cls=None): + def __get__(self, inst: t.Any, cls: t.Any = None) -> types.MethodType | EventHandler: if inst is None: return self return types.MethodType(self.func, inst) class ObserveHandler(EventHandler): - def __init__(self, names: t.Any, type: t.Any) -> None: + def __init__(self, names: tuple[Sentinel | str, ...], type: str = "") -> None: self.trait_names = names self.type = type - def instance_init(self, inst): + def instance_init(self, inst: HasTraits) -> None: inst.observe(self, self.trait_names, type=self.type) class ValidateHandler(EventHandler): - def __init__(self, names: t.Any) -> None: + def __init__(self, names: tuple[Sentinel | str, ...]) -> None: self.trait_names = names - def instance_init(self, inst): + def instance_init(self, inst: HasTraits) -> None: inst._register_validator(self, self.trait_names) @@ -1290,7 +1301,7 @@ class DefaultHandler(EventHandler): def __init__(self, name: str) -> None: self.trait_name = name - def class_init(self, cls, name): + def class_init(self, cls: type[HasTraits], name: str | None) -> None: super().class_init(cls, name) cls._trait_default_generators[self.trait_name] = self @@ -1313,7 +1324,7 @@ def __new__(*args: t.Any, **kwargs: t.Any) -> t.Any: inst.setup_instance(*args, **kwargs) return inst - def setup_instance(*args, **kwargs): + def setup_instance(*args: t.Any, **kwargs: t.Any) -> None: """ This is called **before** self.__init__ is called. """ @@ -1321,7 +1332,7 @@ def setup_instance(*args, **kwargs): self = args[0] args = args[1:] - self._cross_validation_lock = False # type:ignore[attr-defined] + self._cross_validation_lock = False cls = self.__class__ # Let descriptors performance initialization when a HasDescriptor # instance is created. This allows registration of observers and @@ -1335,13 +1346,13 @@ def setup_instance(*args, **kwargs): class HasTraits(HasDescriptors, metaclass=MetaHasTraits): _trait_values: dict[str, t.Any] _static_immutable_initial_values: dict[str, t.Any] - _trait_notifiers: dict[str, t.Any] - _trait_validators: dict[str, t.Any] + _trait_notifiers: dict[str | Sentinel, t.Any] + _trait_validators: dict[str | Sentinel, t.Any] _cross_validation_lock: bool _traits: dict[str, t.Any] _all_trait_default_generators: dict[str, t.Any] - def setup_instance(*args, **kwargs): + def setup_instance(*args: t.Any, **kwargs: t.Any) -> None: # Pass self as args[0] to allow "self" as keyword argument self = args[0] args = args[1:] @@ -1365,7 +1376,7 @@ def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: if kwargs: # this is a simplified (and faster) version of # the hold_trait_notifications(self) context manager - def ignore(*_ignore_args): + def ignore(change: Bunch) -> None: pass self.notify_change = ignore # type:ignore[method-assign] @@ -1389,7 +1400,7 @@ def ignore(*_ignore_args): for key in changed: value = self._traits[key]._cross_validate(self, getattr(self, key)) self.set_trait(key, value) - changes[key]['new'] = value + changes[key]["new"] = value self._cross_validation_lock = False # Restore method retrieval from class del self.notify_change @@ -1447,7 +1458,7 @@ def __setstate__(self, state: dict[str, t.Any]) -> None: @property @contextlib.contextmanager - def cross_validation_lock(self): + def cross_validation_lock(self) -> t.Any: """ A contextmanager for running a block with our cross validation lock set to True. @@ -1466,7 +1477,7 @@ def cross_validation_lock(self): self._cross_validation_lock = False @contextlib.contextmanager - def hold_trait_notifications(self): + def hold_trait_notifications(self) -> t.Any: """Context manager for bundling trait change notifications and cross validation. @@ -1478,9 +1489,9 @@ def hold_trait_notifications(self): yield return else: - cache: dict[str, t.Any] = {} + cache: dict[str, list[Bunch]] = {} - def compress(past_changes, change): + def compress(past_changes: list[Bunch] | None, change: Bunch) -> list[Bunch]: """Merges the provided change with the last if possible.""" if past_changes is None: return [change] @@ -1492,7 +1503,7 @@ def compress(past_changes, change): past_changes.append(change) return past_changes - def hold(change): + def hold(change: Bunch) -> None: name = change.name cache[name] = compress(cache.get(name), change) @@ -1530,7 +1541,7 @@ def hold(change): for change in changes: self.notify_change(change) - def _notify_trait(self, name, old_value, new_value): + def _notify_trait(self, name: str, old_value: t.Any, new_value: t.Any) -> None: self.notify_change( Bunch( name=name, @@ -1550,23 +1561,19 @@ def _notify_observers(self, event: Bunch) -> None: if not isinstance(event, Bunch): # cast to bunch if given a dict event = Bunch(event) # type:ignore[unreachable] - name, type = event['name'], event['type'] + name, type = event["name"], event["type"] callables = [] if name in self._trait_notifiers: callables.extend(self._trait_notifiers.get(name, {}).get(type, [])) callables.extend(self._trait_notifiers.get(name, {}).get(All, [])) - if All in self._trait_notifiers: # type:ignore[comparison-overlap] - callables.extend( - self._trait_notifiers.get(All, {}).get(type, []) # type:ignore[call-overload] - ) - callables.extend( - self._trait_notifiers.get(All, {}).get(All, []) # type:ignore[call-overload] - ) + if All in self._trait_notifiers: + callables.extend(self._trait_notifiers.get(All, {}).get(type, [])) + callables.extend(self._trait_notifiers.get(All, {}).get(All, [])) # Now static ones magic_name = "_%s_changed" % name - if event['type'] == "change" and hasattr(self, magic_name): + if event["type"] == "change" and hasattr(self, magic_name): class_value = getattr(self.__class__, magic_name) if not isinstance(class_value, ObserveHandler): deprecated_method( @@ -1592,7 +1599,9 @@ def _notify_observers(self, event: Bunch) -> None: c(event) - def _add_notifiers(self, handler, name, type): + def _add_notifiers( + self, handler: t.Callable[..., t.Any], name: Sentinel | str, type: str | Sentinel + ) -> None: if name not in self._trait_notifiers: nlist: list[t.Any] = [] self._trait_notifiers[name] = {type: nlist} @@ -1605,7 +1614,9 @@ def _add_notifiers(self, handler, name, type): if handler not in nlist: nlist.append(handler) - def _remove_notifiers(self, handler, name, type): + def _remove_notifiers( + self, handler: t.Callable[..., t.Any] | None, name: Sentinel | str, type: str | Sentinel + ) -> None: try: if handler is None: del self._trait_notifiers[name][type] @@ -1614,7 +1625,12 @@ def _remove_notifiers(self, handler, name, type): except KeyError: pass - def on_trait_change(self, handler=None, name=None, remove=False): + def on_trait_change( + self, + handler: EventHandler | None = None, + name: Sentinel | str | None = None, + remove: bool = False, + ) -> None: """DEPRECATED: Setup a handler to be called when a trait changes. This is used to setup dynamic notifications of trait changes. @@ -1717,14 +1733,16 @@ def unobserve_all(self, name: str | t.Any = All) -> None: """Remove trait change handlers of any type for the specified name. If name is not specified, removes all trait notifiers.""" if name is All: - self._trait_notifiers: dict[str, t.Any] = {} + self._trait_notifiers = {} else: try: del self._trait_notifiers[name] except KeyError: pass - def _register_validator(self, handler, names): + def _register_validator( + self, handler: t.Callable[..., None], names: tuple[str | Sentinel, ...] + ) -> None: """Setup a handler to be called when a trait should be cross validated. This is used to setup dynamic notifications for cross-validation. @@ -1788,7 +1806,7 @@ def class_trait_names(cls: type[HasTraits], **metadata: t.Any) -> list[str]: return list(cls.class_traits(**metadata)) @classmethod - def class_traits(cls: type[HasTraits], **metadata: t.Any) -> dict[str, TraitType]: + def class_traits(cls: type[HasTraits], **metadata: t.Any) -> dict[str, TraitType[t.Any, t.Any]]: """Get a ``dict`` of all the traits of this class. The dictionary is keyed on the name and the values are the TraitType objects. @@ -1822,7 +1840,9 @@ def class_traits(cls: type[HasTraits], **metadata: t.Any) -> dict[str, TraitType return result @classmethod - def class_own_traits(cls: type[HasTraits], **metadata: t.Any) -> dict[str, TraitType]: + def class_own_traits( + cls: type[HasTraits], **metadata: t.Any + ) -> dict[str, TraitType[t.Any, t.Any]]: """Get a dict of all the traitlets defined on this class, not a parent. Works like `class_traits`, except for excluding traits from parents. @@ -1883,7 +1903,7 @@ def trait_values(self, **metadata: t.Any) -> dict[str, t.Any]: """ return {name: getattr(self, name) for name in self.trait_names(**metadata)} - def _get_trait_default_generator(self, name): + def _get_trait_default_generator(self, name: str) -> t.Any: """Return default generator for a given trait Walk the MRO to resolve the correct default generator according to inheritance. @@ -1895,7 +1915,7 @@ def _get_trait_default_generator(self, name): return getattr(self.__class__, method_name) return self._all_trait_default_generators[name] - def trait_defaults(self, *names: str, **metadata: t.Any) -> dict[str, t.Any]: + def trait_defaults(self, *names: str, **metadata: t.Any) -> dict[str, t.Any] | Sentinel: """Return a trait's default value or a dictionary of them Notes @@ -1907,7 +1927,7 @@ def trait_defaults(self, *names: str, **metadata: t.Any) -> dict[str, t.Any]: raise TraitError(f"'{n}' is not a trait of '{type(self).__name__}' instances") if len(names) == 1 and len(metadata) == 0: - return self._get_trait_default_generator(names[0])(self) + return t.cast(Sentinel, self._get_trait_default_generator(names[0])(self)) trait_names = self.trait_names(**metadata) trait_names.extend(names) @@ -1921,7 +1941,7 @@ def trait_names(self, **metadata: t.Any) -> list[str]: """Get a list of all the names of this class' traits.""" return list(self.traits(**metadata)) - def traits(self, **metadata: t.Any) -> dict[str, TraitType]: + def traits(self, **metadata: t.Any) -> dict[str, TraitType[t.Any, t.Any]]: """Get a ``dict`` of all the traits of this class. The dictionary is keyed on the name and the values are the TraitType objects. @@ -2021,7 +2041,7 @@ class ClassBasedTraitType(TraitType[G, S]): Instance and This. """ - def _resolve_string(self, string): + def _resolve_string(self, string: str) -> t.Any: """ Resolve a string supplied for a type into an actual object. """ @@ -2145,7 +2165,7 @@ def __init__( **kwargs, ) - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: """Validates that the value is a valid object instance.""" if isinstance(value, str): try: @@ -2157,13 +2177,13 @@ def validate(self, obj, value): ) from e try: if issubclass(value, self.klass): # type:ignore[arg-type] - return value + return t.cast(G, value) except Exception: pass self.error(obj, value) - def info(self): + def info(self) -> str: """Returns a description of the trait.""" if isinstance(self.klass, str): klass = self.klass @@ -2174,18 +2194,18 @@ def info(self): return result + " or None" return result - def instance_init(self, obj): + def instance_init(self, obj: t.Any) -> None: # we can't do this in subclass_init because that # might be called before all imports are done. self._resolve_classes() - def _resolve_classes(self): + def _resolve_classes(self) -> None: if isinstance(self.klass, str): self.klass = self._resolve_string(self.klass) if isinstance(self.default_value, str): self.default_value = self._resolve_string(self.default_value) - def default_value_repr(self): + def default_value_repr(self) -> str: value = self.default_value assert value is not None if isinstance(value, str): @@ -2314,14 +2334,16 @@ class or its subclasses. Our implementation is quite different super().__init__(allow_none=allow_none, read_only=read_only, help=help, **kwargs) - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> T | None: assert self.klass is not None - if isinstance(value, self.klass): # type:ignore[arg-type] + if self.allow_none and value is None: return value + if isinstance(value, self.klass): # type:ignore[arg-type] + return t.cast(T, value) else: self.error(obj, value) - def info(self): + def info(self) -> str: if isinstance(self.klass, str): result = add_article(self.klass) else: @@ -2330,28 +2352,26 @@ def info(self): result += " or None" return result - def instance_init(self, obj): + def instance_init(self, obj: t.Any) -> None: # we can't do this in subclass_init because that # might be called before all imports are done. self._resolve_classes() - def _resolve_classes(self): + def _resolve_classes(self) -> None: if isinstance(self.klass, str): self.klass = self._resolve_string(self.klass) - def make_dynamic_default(self): + def make_dynamic_default(self) -> T | None: if (self.default_args is None) and (self.default_kwargs is None): return None assert self.klass is not None - return self.klass( - *(self.default_args or ()), **(self.default_kwargs or {}) - ) # type:ignore[operator] + return self.klass(*(self.default_args or ()), **(self.default_kwargs or {})) # type:ignore[operator] - def default_value_repr(self): + def default_value_repr(self) -> str: return repr(self.make_dynamic_default()) - def from_string(self, s): - return _safe_literal_eval(s) + def from_string(self, s: str) -> T | None: + return t.cast(T, _safe_literal_eval(s)) class ForwardDeclaredMixin: @@ -2359,7 +2379,7 @@ class ForwardDeclaredMixin: Mixin for forward-declared versions of Instance and Type. """ - def _resolve_string(self, string): + def _resolve_string(self, string: str) -> t.Any: """ Find the specified class name by looking for it in the module in which our this_class attribute was defined. @@ -2397,7 +2417,7 @@ class This(ClassBasedTraitType[t.Optional[T], t.Optional[T]]): def __init__(self, **kwargs: t.Any) -> None: super().__init__(None, **kwargs) - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> HasTraits | None: # What if value is a superclass of obj.__class__? This is # complicated if it was the superclass that defined the This # trait. @@ -2443,7 +2463,7 @@ def __init__(self, trait_types: t.Any, **kwargs: t.Any) -> None: self.info_text = " or ".join([tt.info() for tt in self.trait_types]) super().__init__(**kwargs) - def default(self, obj=None): + def default(self, obj: t.Any = None) -> t.Any: default = super().default(obj) for trait in self.trait_types: if default is Undefined: @@ -2452,18 +2472,18 @@ def default(self, obj=None): break return default - def class_init(self, cls, name): + def class_init(self, cls: type[HasTraits], name: str | None) -> None: for trait_type in reversed(self.trait_types): trait_type.class_init(cls, None) super().class_init(cls, name) - def subclass_init(self, cls): + def subclass_init(self, cls: type[t.Any]) -> None: for trait_type in reversed(self.trait_types): trait_type.subclass_init(cls) # explicitly not calling super().subclass_init(cls) # to opt out of instance_init - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> t.Any: with obj.cross_validation_lock: for trait_type in self.trait_types: try: @@ -2476,13 +2496,13 @@ def validate(self, obj, value): continue self.error(obj, value) - def __or__(self, other): + def __or__(self, other: t.Any) -> Union: if isinstance(other, Union): return Union(self.trait_types + other.trait_types) else: return Union([*self.trait_types, other]) - def from_string(self, s): + def from_string(self, s: str) -> t.Any: for trait_type in self.trait_types: try: v = trait_type.from_string(s) @@ -2568,11 +2588,13 @@ def __get__(self, obj: t.Any | None, cls: type[t.Any]) -> t.Any | Any: allow_none = True info_text = "any value" - def subclass_init(self, cls): + def subclass_init(self, cls: type[t.Any]) -> None: pass # fully opt out of instance_init -def _validate_bounds(trait, obj, value): +def _validate_bounds( + trait: Int[t.Any, t.Any] | Float[t.Any, t.Any], obj: t.Any, value: t.Any +) -> t.Any: """ Validate that a number to be applied to a trait is between bounds. @@ -2647,17 +2669,17 @@ def __init__( **kwargs, ) - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: if not isinstance(value, int): self.error(obj, value) - return _validate_bounds(self, obj, value) + return t.cast(G, _validate_bounds(self, obj, value)) - def from_string(self, s): + def from_string(self, s: str) -> G: if self.allow_none and s == "None": - return None - return int(s) + return t.cast(G, None) + return t.cast(G, int(s)) - def subclass_init(self, cls): + def subclass_init(self, cls: type[t.Any]) -> None: pass # fully opt out of instance_init @@ -2701,12 +2723,12 @@ def __init__( ) -> None: ... - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: try: value = int(value) except Exception: self.error(obj, value) - return _validate_bounds(self, obj, value) + return t.cast(G, _validate_bounds(self, obj, value)) Long, CLong = Int, CInt @@ -2763,19 +2785,19 @@ def __init__( **kwargs, ) - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: if isinstance(value, int): value = float(value) if not isinstance(value, float): self.error(obj, value) - return _validate_bounds(self, obj, value) + return t.cast(G, _validate_bounds(self, obj, value)) - def from_string(self, s): + def from_string(self, s: str) -> G: if self.allow_none and s == "None": - return None - return float(s) + return t.cast(G, None) + return t.cast(G, float(s)) - def subclass_init(self, cls): + def subclass_init(self, cls: type[t.Any]) -> None: pass # fully opt out of instance_init @@ -2819,12 +2841,12 @@ def __init__( ) -> None: ... - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: try: value = float(value) except Exception: self.error(obj, value) - return _validate_bounds(self, obj, value) + return t.cast(G, _validate_bounds(self, obj, value)) class Complex(TraitType[complex, t.Union[complex, float, int]]): @@ -2833,26 +2855,26 @@ class Complex(TraitType[complex, t.Union[complex, float, int]]): default_value = 0.0 + 0.0j info_text = "a complex number" - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> complex | None: if isinstance(value, complex): return value if isinstance(value, (float, int)): return complex(value) self.error(obj, value) - def from_string(self, s): + def from_string(self, s: str) -> complex | None: if self.allow_none and s == "None": return None return complex(s) - def subclass_init(self, cls): + def subclass_init(self, cls: type[t.Any]) -> None: pass # fully opt out of instance_init class CComplex(Complex, TraitType[complex, t.Any]): """A casting version of the complex number trait.""" - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> complex | None: try: return complex(value) except Exception: @@ -2868,12 +2890,12 @@ class Bytes(TraitType[bytes, bytes]): default_value = b"" info_text = "a bytes object" - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> bytes | None: if isinstance(value, bytes): return value self.error(obj, value) - def from_string(self, s): + def from_string(self, s: str) -> bytes | None: if self.allow_none and s == "None": return None if len(s) >= 3: @@ -2891,14 +2913,14 @@ def from_string(self, s): break return s.encode("utf8") - def subclass_init(self, cls): + def subclass_init(self, cls: type[t.Any]) -> None: pass # fully opt out of instance_init class CBytes(Bytes, TraitType[bytes, t.Any]): """A casting version of the byte string trait.""" - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> bytes | None: try: return bytes(value) except Exception: @@ -2948,20 +2970,20 @@ def __init__( ) -> None: ... - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: if isinstance(value, str): - return value + return t.cast(G, value) if isinstance(value, bytes): try: - return value.decode("ascii", "strict") + return t.cast(G, value.decode("ascii", "strict")) except UnicodeDecodeError as e: msg = "Could not decode {!r} for unicode trait '{}' of {} instance." raise TraitError(msg.format(value, self.name, class_of(obj))) from e self.error(obj, value) - def from_string(self, s): + def from_string(self, s: str) -> G: if self.allow_none and s == "None": - return None + return t.cast(G, None) s = os.path.expanduser(s) if len(s) >= 2: # handle deprecated "1" @@ -2975,9 +2997,9 @@ def from_string(self, s): DeprecationWarning, stacklevel=2, ) - return s + return t.cast(G, s) - def subclass_init(self, cls): + def subclass_init(self, cls: type[t.Any]) -> None: pass # fully opt out of instance_init @@ -3021,9 +3043,9 @@ def __init__( ) -> None: ... - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: try: - return str(value) + return t.cast(G, str(value)) except Exception: self.error(obj, value) @@ -3037,14 +3059,14 @@ class ObjectName(TraitType[str, str]): coerce_str = staticmethod(lambda _, s: s) - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> str: value = self.coerce_str(obj, value) if isinstance(value, str) and isidentifier(value): return value self.error(obj, value) - def from_string(self, s): + def from_string(self, s: str) -> str | None: if self.allow_none and s == "None": return None return s @@ -3053,7 +3075,7 @@ def from_string(self, s): class DottedObjectName(ObjectName): """A string holding a valid dotted object name in Python, such as A.b3._c""" - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> str: value = self.coerce_str(obj, value) if isinstance(value, str) and all(isidentifier(a) for a in value.split(".")): @@ -3104,31 +3126,31 @@ def __init__( ) -> None: ... - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: if isinstance(value, bool): - return value + return t.cast(G, value) elif isinstance(value, int): if value == 1: - return True + return t.cast(G, True) elif value == 0: - return False + return t.cast(G, False) self.error(obj, value) - def from_string(self, s): + def from_string(self, s: str) -> G: if self.allow_none and s == "None": - return None + return t.cast(G, None) s = s.lower() if s in {"true", "1"}: - return True + return t.cast(G, True) elif s in {"false", "0"}: - return False + return t.cast(G, False) else: raise ValueError("%r is not 1, 0, true, or false") - def subclass_init(self, cls): + def subclass_init(self, cls: type[t.Any]) -> None: pass # fully opt out of instance_init - def argcompleter(self, **kwargs): + def argcompleter(self, **kwargs: t.Any) -> list[str]: """Completion hints for argcomplete""" completions = ["true", "1", "false", "0"] if self.allow_none: @@ -3176,9 +3198,9 @@ def __init__( ) -> None: ... - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: try: - return bool(value) + return t.cast(G, bool(value)) except Exception: self.error(obj, value) @@ -3194,41 +3216,41 @@ def __init__( default_value = None super().__init__(default_value, **kwargs) - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: if value in self.values: - return value + return t.cast(G, value) self.error(obj, value) - def _choices_str(self, as_rst=False): + def _choices_str(self, as_rst: bool = False) -> str: """Returns a description of the trait choices (not none).""" choices = self.values if as_rst: choices = "|".join("``%r``" % x for x in choices) else: choices = repr(list(choices)) - return choices + return t.cast(str, choices) - def _info(self, as_rst=False): + def _info(self, as_rst: bool = False) -> str: """Returns a description of the trait.""" none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else "" return f"any of {self._choices_str(as_rst)}{none}" - def info(self): + def info(self) -> str: return self._info(as_rst=False) - def info_rst(self): + def info_rst(self) -> str: return self._info(as_rst=True) - def from_string(self, s): + def from_string(self, s: str) -> G: try: return self.validate(None, s) except TraitError: - return _safe_literal_eval(s) + return t.cast(G, _safe_literal_eval(s)) - def subclass_init(self, cls): + def subclass_init(self, cls: type[t.Any]) -> None: pass # fully opt out of instance_init - def argcompleter(self, **kwargs): + def argcompleter(self, **kwargs: t.Any) -> list[str]: """Completion hints for argcomplete""" return [str(v) for v in self.values] @@ -3244,24 +3266,24 @@ def __init__( ) -> None: super().__init__(values, default_value=default_value, **kwargs) - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: if not isinstance(value, str): self.error(obj, value) for v in self.values: if v.lower() == value.lower(): - return v + return t.cast(G, v) self.error(obj, value) - def _info(self, as_rst=False): + def _info(self, as_rst: bool = False) -> str: """Returns a description of the trait.""" none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else "" return f"any of {self._choices_str(as_rst)} (case-insensitive){none}" - def info(self): + def info(self) -> str: return self._info(as_rst=False) - def info_rst(self): + def info_rst(self) -> str: return self._info(as_rst=True) @@ -3284,34 +3306,34 @@ def __init__( self.substring_matching = substring_matching super().__init__(values, default_value=default_value, **kwargs) - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: if not isinstance(value, str): self.error(obj, value) conv_func = (lambda c: c) if self.case_sensitive else lambda c: c.lower() substring_matching = self.substring_matching match_func = (lambda v, c: v in c) if substring_matching else (lambda v, c: c.startswith(v)) - value = conv_func(value) + value = conv_func(value) # type:ignore[no-untyped-call] choices = self.values - matches = [match_func(value, conv_func(c)) for c in choices] + matches = [match_func(value, conv_func(c)) for c in choices] # type:ignore[no-untyped-call] if sum(matches) == 1: for v, m in zip(choices, matches): if m: - return v + return t.cast(G, v) self.error(obj, value) - def _info(self, as_rst=False): + def _info(self, as_rst: bool = False) -> str: """Returns a description of the trait.""" none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else "" case = "sensitive" if self.case_sensitive else "insensitive" substr = "substring" if self.substring_matching else "prefix" return f"any case-{case} {substr} of {self._choices_str(as_rst)}{none}" - def info(self): + def info(self) -> str: return self._info(as_rst=False) - def info_rst(self): + def info_rst(self) -> str: return self._info(as_rst=True) @@ -3324,7 +3346,7 @@ class Container(Instance[T]): klass: type[T] | None = None _cast_types: t.Any = () _valid_defaults = SequenceTypes - _trait = None + _trait: t.Any = None _literal_from_string_pairs: t.Any = ("[]", "()") @t.overload @@ -3446,7 +3468,7 @@ def __init__( klass=self.klass, args=args, help=help, read_only=read_only, config=config, **kwargs ) - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> T | None: if isinstance(value, self._cast_types): assert self.klass is not None value = self.klass(value) # type:ignore[call-arg] @@ -3456,12 +3478,12 @@ def validate(self, obj, value): value = self.validate_elements(obj, value) - return value + return t.cast(T, value) - def validate_elements(self, obj, value): + def validate_elements(self, obj: t.Any, value: t.Any) -> T | None: validated = [] if self._trait is None or isinstance(self._trait, Any): - return value + return t.cast(T, value) for v in value: try: v = self._trait._validate(obj, v) @@ -3472,18 +3494,18 @@ def validate_elements(self, obj, value): assert self.klass is not None return self.klass(validated) # type:ignore[call-arg] - def class_init(self, cls, name): + def class_init(self, cls: type[t.Any], name: str | None) -> None: if isinstance(self._trait, TraitType): self._trait.class_init(cls, None) super().class_init(cls, name) - def subclass_init(self, cls): + def subclass_init(self, cls: type[t.Any]) -> None: if isinstance(self._trait, TraitType): self._trait.subclass_init(cls) # explicitly not calling super().subclass_init(cls) # to opt out of instance_init - def from_string(self, s): + def from_string(self, s: str) -> T | None: """Load value from a single string""" if not isinstance(s, str): raise TraitError(f"Expected string, got {s!r}") @@ -3493,7 +3515,7 @@ def from_string(self, s): test = None return self.validate(None, test) - def from_string_list(self, s_list): + def from_string_list(self, s_list: list[str]) -> T | None: """Return the value from a list of config strings This is where we parse CLI configuration @@ -3527,20 +3549,20 @@ def from_string_list(self, s_list): item_from_string = self.item_from_string else: # backward-compat: allow item_from_string to ignore index arg - def item_from_string(s, index=None): - return self.item_from_string(s) + def item_from_string(s: str, index: int | None = None) -> T | str: + return t.cast(T, self.item_from_string(s)) return self.klass( # type:ignore[call-arg] [item_from_string(s, index=idx) for idx, s in enumerate(s_list)] ) - def item_from_string(self, s, index=None): + def item_from_string(self, s: str, index: int | None = None) -> T | str: """Cast a single item from a string Evaluated when parsing CLI configuration from a string """ if self._trait: - return self._trait.from_string(s) + return t.cast(T, self._trait.from_string(s)) else: return s @@ -3589,21 +3611,21 @@ def __init__( self._minlen = minlen super().__init__(trait=trait, default_value=default_value, **kwargs) - def length_error(self, obj, value): + def length_error(self, obj: t.Any, value: t.Any) -> None: e = ( "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." % (self.name, class_of(obj), self._minlen, self._maxlen, value) ) raise TraitError(e) - def validate_elements(self, obj, value): + def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any: length = len(value) if length < self._minlen or length > self._maxlen: self.length_error(obj, value) return super().validate_elements(obj, value) - def set(self, obj, value): + def set(self, obj: t.Any, value: t.Any) -> None: if isinstance(value, str): return super().set(obj, [value]) else: @@ -3657,21 +3679,21 @@ def __init__( self._minlen = minlen super().__init__(trait=trait, default_value=default_value, **kwargs) - def length_error(self, obj, value): + def length_error(self, obj: t.Any, value: t.Any) -> None: e = ( "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." % (self.name, class_of(obj), self._minlen, self._maxlen, value) ) raise TraitError(e) - def validate_elements(self, obj, value): + def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any: length = len(value) if length < self._minlen or length > self._maxlen: self.length_error(obj, value) return super().validate_elements(obj, value) - def set(self, obj, value): + def set(self, obj: t.Any, value: t.Any) -> None: if isinstance(value, str): return super().set( obj, @@ -3682,9 +3704,9 @@ def set(self, obj, value): else: return super().set(obj, value) - def default_value_repr(self): + def default_value_repr(self) -> str: # Ensure default value is sorted for a reproducible build - list_repr = repr(sorted(self.make_dynamic_default())) + list_repr = repr(sorted(self.make_dynamic_default() or [])) if list_repr == "[]": return "set()" return "{" + list_repr[1:-1] + "}" @@ -3773,7 +3795,7 @@ def __init__(self, *traits: t.Any, **kwargs: t.Any) -> None: args = None super(Container, self).__init__(klass=self.klass, args=args, **kwargs) - def item_from_string(self, s, index): + def item_from_string(self, s: str, index: int) -> t.Any: # type:ignore[override] """Cast a single item from a string Evaluated when parsing CLI configuration from a string @@ -3784,7 +3806,7 @@ def item_from_string(self, s, index): return s return self._traits[index].from_string(s) - def validate_elements(self, obj, value): + def validate_elements(self, obj: t.Any, value: t.Any) -> t.Any: if not self._traits: # nothing to validate return value @@ -3805,13 +3827,13 @@ def validate_elements(self, obj, value): validated.append(v) return tuple(validated) - def class_init(self, cls, name): + def class_init(self, cls: type[t.Any], name: str | None) -> None: for trait in self._traits: if isinstance(trait, TraitType): trait.class_init(cls, None) super(Container, self).class_init(cls, name) - def subclass_init(self, cls): + def subclass_init(self, cls: type[t.Any]) -> None: for trait in self._traits: if isinstance(trait, TraitType): trait.subclass_init(cls) @@ -3964,21 +3986,23 @@ def __init__( super().__init__(klass=dict, args=args, **kwargs) - def element_error(self, obj, element, validator, side="Values"): + def element_error( + self, obj: t.Any, element: t.Any, validator: t.Any, side: str = "Values" + ) -> None: e = ( side + f" of the '{self.name}' trait of {class_of(obj)} instance must be {validator.info()}, but a value of {repr_type(element)} was specified." ) raise TraitError(e) - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> dict[str, t.Any] | None: value = super().validate(obj, value) if value is None: return value - value = self.validate_elements(obj, value) - return value + value_dict = self.validate_elements(obj, value) + return value_dict - def validate_elements(self, obj, value): + def validate_elements(self, obj: t.Any, value: dict[t.Any, t.Any]) -> dict[t.Any, t.Any] | None: per_key_override = self._per_key_traits or {} key_trait = self._key_trait value_trait = self._value_trait @@ -4003,7 +4027,7 @@ def validate_elements(self, obj, value): return self.klass(validated) # type:ignore[misc,operator] - def class_init(self, cls, name): + def class_init(self, cls: type[t.Any], name: str | None) -> None: if isinstance(self._value_trait, TraitType): self._value_trait.class_init(cls, None) if isinstance(self._key_trait, TraitType): @@ -4013,7 +4037,7 @@ def class_init(self, cls, name): trait.class_init(cls, None) super().class_init(cls, name) - def subclass_init(self, cls): + def subclass_init(self, cls: type[t.Any]) -> None: if isinstance(self._value_trait, TraitType): self._value_trait.subclass_init(cls) if isinstance(self._key_trait, TraitType): @@ -4024,7 +4048,7 @@ def subclass_init(self, cls): # explicitly not calling super().subclass_init(cls) # to opt out of instance_init - def from_string(self, s): + def from_string(self, s: str) -> t.Any: """Load value from a single string""" if not isinstance(s, str): raise TypeError(f"from_string expects a string, got {s!r} of type {type(s)}") @@ -4036,7 +4060,7 @@ def from_string(self, s): return test raise - def from_string_list(self, s_list): + def from_string_list(self, s_list: list[str]) -> t.Any: """Return a dict from a list of config strings. This is where we parse CLI configuration. @@ -4062,7 +4086,7 @@ def from_string_list(self, s_list): combined.update(d) return combined - def item_from_string(self, s): + def item_from_string(self, s: str) -> dict[t.Any, t.Any]: """Cast a single-key dict from a string. Evaluated when parsing CLI configuration from a string. @@ -4137,23 +4161,23 @@ def __init__( ) -> None: ... - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> G: if isinstance(value, tuple): if len(value) == 2: if isinstance(value[0], str) and isinstance(value[1], int): port = value[1] if port >= 0 and port <= 65535: - return value + return t.cast(G, value) self.error(obj, value) - def from_string(self, s): + def from_string(self, s: str) -> G: if self.allow_none and s == "None": - return None + return t.cast(G, None) if ":" not in s: raise ValueError("Require `ip:port`, got %r" % s) - ip, port = s.split(":", 1) - port = int(port) - return (ip, port) + ip, port_str = s.split(":", 1) + port = int(port_str) + return t.cast(G, (ip, port)) class CRegExp(TraitType["re.Pattern[t.Any]", t.Union["re.Pattern[t.Any]", str]]): @@ -4164,7 +4188,7 @@ class CRegExp(TraitType["re.Pattern[t.Any]", t.Union["re.Pattern[t.Any]", str]]) info_text = "a regular expression" - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> re.Pattern[t.Any] | None: try: return re.compile(value) except Exception: @@ -4215,7 +4239,7 @@ def __init__( self.enum_class = enum_class self.name_prefix = enum_class.__name__ + "." - def select_by_number(self, value, default=Undefined): + def select_by_number(self, value: int, default: t.Any = Undefined) -> t.Any: """Selects enum-value by using its number-constant.""" assert isinstance(value, int) enum_members = self.enum_class.__members__ @@ -4225,7 +4249,7 @@ def select_by_number(self, value, default=Undefined): # -- NOT FOUND: return default - def select_by_name(self, value, default=Undefined): + def select_by_name(self, value: str, default: t.Any = Undefined) -> t.Any: """Selects enum-value by using its name or scoped-name.""" assert isinstance(value, str) if value.startswith(self.name_prefix): @@ -4233,7 +4257,7 @@ def select_by_name(self, value, default=Undefined): value = value.replace(self.name_prefix, "", 1) return self.enum_class.__members__.get(value, default) - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> t.Any: if isinstance(value, self.enum_class): return value elif isinstance(value, int): @@ -4253,7 +4277,7 @@ def validate(self, obj, value): return self.default_value self.error(obj, value) - def _choices_str(self, as_rst=False): + def _choices_str(self, as_rst: bool = False) -> str: """Returns a description of the trait choices (not none).""" choices = self.enum_class.__members__.keys() if as_rst: @@ -4261,15 +4285,15 @@ def _choices_str(self, as_rst=False): else: return repr(list(choices)) # Listify because py3.4- prints odict-class - def _info(self, as_rst=False): + def _info(self, as_rst: bool = False) -> str: """Returns a description of the trait.""" none = " or %s" % ("`None`" if as_rst else "None") if self.allow_none else "" return f"any of {self._choices_str(as_rst)}{none}" - def info(self): + def info(self) -> str: return self._info(as_rst=False) - def info_rst(self): + def info_rst(self) -> str: return self._info(as_rst=True) @@ -4283,7 +4307,7 @@ class Callable(TraitType[t.Callable[..., t.Any], t.Callable[..., t.Any]]): info_text = "a callable" - def validate(self, obj, value): + def validate(self, obj: t.Any, value: t.Any) -> t.Any: if callable(value): return value else: