Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typing upgrades #868

Merged
merged 4 commits into from
Sep 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion traitlets/config/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,7 +446,7 @@ def _show_config_changed(self, change):
self._save_start = self.start
self.start = self.start_show_config # type:ignore[method-assign]

def __init__(self, **kwargs):
def __init__(self, **kwargs: t.Any) -> None:
SingletonConfigurable.__init__(self, **kwargs)
# Ensure my class is in self.classes, so my attributes appear in command line
# options and config files.
Expand Down
3 changes: 2 additions & 1 deletion traitlets/config/configurable.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@


import logging
import typing as t
from copy import deepcopy
from textwrap import dedent

Expand Down Expand Up @@ -46,7 +47,7 @@ class Configurable(HasTraits):
config = Instance(Config, (), {})
parent = Instance("traitlets.config.configurable.Configurable", allow_none=True)

def __init__(self, **kwargs):
def __init__(self, **kwargs: t.Any) -> None:
"""Create a configurable given a config config.

Parameters
Expand Down
39 changes: 18 additions & 21 deletions traitlets/config/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import argparse
import copy
Expand Down Expand Up @@ -236,7 +237,7 @@ class Config(dict): # type:ignore[type-arg]

"""

def __init__(self, *args, **kwds):
def __init__(self, *args: t.Any, **kwds: t.Any) -> None:
dict.__init__(self, *args, **kwds)
self._ensure_subconfig()

Expand Down Expand Up @@ -273,15 +274,15 @@ def merge(self, other):

self.update(to_update)

def collisions(self, other: "Config") -> t.Dict[str, t.Any]:
def collisions(self, other: Config) -> dict[str, t.Any]:
"""Check for collisions between two config objects.

Returns a dict of the form {"Class": {"trait": "collision message"}}`,
indicating which values have been ignored.

An empty dict indicates no collisions.
"""
collisions: t.Dict[str, t.Any] = {}
collisions: dict[str, t.Any] = {}
for section in self:
if section not in other:
continue
Expand Down Expand Up @@ -490,7 +491,7 @@ def _log_default(self):

return get_logger()

def __init__(self, log=None):
def __init__(self, log: t.Any = None) -> None:
"""A base class for config loaders.

log : instance of :class:`logging.Logger` to use.
Expand Down Expand Up @@ -532,7 +533,7 @@ class FileConfigLoader(ConfigLoader):
here.
"""

def __init__(self, filename, path=None, **kw):
def __init__(self, filename: str, path: str | None = None, **kw: t.Any) -> None:
"""Build a config loader for a filename and path.

Parameters
Expand Down Expand Up @@ -795,12 +796,12 @@ class ArgParseConfigLoader(CommandLineConfigLoader):

def __init__(
self,
argv: t.Optional[t.List[str]] = None,
aliases: t.Optional[t.Dict[Flags, str]] = None,
flags: t.Optional[t.Dict[Flags, str]] = None,
argv: list[str] | None = None,
aliases: dict[Flags, str] | None = None,
flags: dict[Flags, str] | None = None,
log: t.Any = None,
classes: t.Optional[t.List[t.Type[t.Any]]] = None,
subcommands: t.Optional[SubcommandsDict] = None,
classes: list[type[t.Any]] | None = None,
subcommands: SubcommandsDict | None = None,
*parser_args: t.Any,
**parser_kw: t.Any,
) -> None:
Expand Down Expand Up @@ -899,17 +900,15 @@ def _create_parser(self):
def _add_arguments(self, aliases, flags, classes):
raise NotImplementedError("subclasses must implement _add_arguments")

def _argcomplete(
self, classes: t.List[t.Any], subcommands: t.Optional[SubcommandsDict]
) -> None:
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):
"""self.parser->self.parsed_data"""
uargs = [cast_unicode(a) for a in args]

unpacked_aliases: t.Dict[str, str] = {}
unpacked_aliases: dict[str, str] = {}
if self.aliases:
unpacked_aliases = {}
for alias, alias_target in self.aliases.items():
Expand Down Expand Up @@ -957,7 +956,7 @@ def _convert_to_config(self):
class _FlagAction(argparse.Action):
"""ArgParse action to handle a flag"""

def __init__(self, *args, **kwargs):
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
self.flag = kwargs.pop("flag")
self.alias = kwargs.pop("alias", None)
kwargs["const"] = Undefined
Expand All @@ -983,8 +982,8 @@ class KVArgParseConfigLoader(ArgParseConfigLoader):
parser_class = _KVArgParser # type:ignore[assignment]

def _add_arguments(self, aliases, flags, classes):
alias_flags: t.Dict[str, t.Any] = {}
argparse_kwds: t.Dict[str, t.Any]
alias_flags: dict[str, t.Any] = {}
argparse_kwds: dict[str, t.Any]
paa = self.parser.add_argument
self.parser.set_defaults(_flags=[])
paa("extra_args", nargs="*")
Expand Down Expand Up @@ -1108,9 +1107,7 @@ def _handle_unrecognized_alias(self, arg: str) -> None:
"""
self.log.warning("Unrecognized alias: '%s', it will have no effect.", arg)

def _argcomplete(
self, classes: t.List[t.Any], subcommands: t.Optional[SubcommandsDict]
) -> None:
def _argcomplete(self, classes: list[t.Any], subcommands: SubcommandsDict | None) -> None:
"""If argcomplete is enabled, allow triggering command-line autocompletion"""
try:
import argcomplete # noqa
Expand All @@ -1132,7 +1129,7 @@ class KeyValueConfigLoader(KVArgParseConfigLoader):
Use KVArgParseConfigLoader
"""

def __init__(self, *args, **kwargs):
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
warnings.warn(
"KeyValueConfigLoader is deprecated since Traitlets 5.0."
" Use KVArgParseConfigLoader instead.",
Expand Down
5 changes: 3 additions & 2 deletions traitlets/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

# Copyright (c) IPython Development Team.
# Distributed under the terms of the Modified BSD License.
from __future__ import annotations

import logging

_logger = None
_logger: logging.Logger | None = None


def get_logger():
def get_logger() -> logging.Logger:
"""Grab the global logger instance.

If a global Application is instantiated, grab its logger.
Expand Down
203 changes: 202 additions & 1 deletion traitlets/tests/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,220 @@

import pytest

from traitlets import Bool, CInt, HasTraits, Instance, Int, TCPAddress
from traitlets import (
Any,
Bool,
CInt,
Dict,
HasTraits,
Instance,
Int,
List,
Set,
TCPAddress,
Type,
Unicode,
Union,
default,
observe,
validate,
)
from traitlets.config import Config

if not typing.TYPE_CHECKING:

def reveal_type(*args, **kwargs):
pass


# mypy: disallow-untyped-calls


class Foo:
def __init__(self, c):
self.c = c


@pytest.mark.mypy_testing
def mypy_decorator_typing():
class T(HasTraits):
foo = Unicode("").tag(config=True)

@default("foo")
def _default_foo(self) -> str:
return "hi"

@observe("foo")
def _foo_observer(self, change: typing.Any) -> bool:
return True

@validate("foo")
def _foo_validate(self, commit: typing.Any) -> bool:
return True

t = T()
reveal_type(t.foo) # R: builtins.str
reveal_type(t._foo_observer) # R: Any
reveal_type(t._foo_validate) # R: Any


@pytest.mark.mypy_testing
def mypy_config_typing():
c = Config(
{
"ExtractOutputPreprocessor": {"enabled": True},
}
)
reveal_type(c) # R: traitlets.config.loader.Config


@pytest.mark.mypy_testing
def mypy_union_typing():
class T(HasTraits):
style = Union(
[Unicode("default"), Type(klass=object)],
help="Name of the pygments style to use",
default_value="hi",
).tag(config=True)

t = T()
reveal_type(Union("foo")) # R: traitlets.traitlets.Union
reveal_type(Union("").tag(sync=True)) # R: traitlets.traitlets.Union
reveal_type(Union(None, allow_none=True)) # R: traitlets.traitlets.Union
reveal_type(Union(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.Union
reveal_type(T.style) # R: traitlets.traitlets.Union
reveal_type(t.style) # R: Any


@pytest.mark.mypy_testing
def mypy_list_typing():
class T(HasTraits):
latex_command = List(
["xelatex", "{filename}", "-quiet"], help="Shell command used to compile latex."
).tag(config=True)

t = T()
reveal_type(List("foo")) # R: traitlets.traitlets.List
reveal_type(List("").tag(sync=True)) # R: traitlets.traitlets.List
reveal_type(List(None, allow_none=True)) # R: traitlets.traitlets.List
reveal_type(List(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.List
reveal_type(T.latex_command) # R: traitlets.traitlets.List
reveal_type(t.latex_command) # R: builtins.list[Any]


@pytest.mark.mypy_testing
def mypy_dict_typing():
class T(HasTraits):
foo = Dict({}, help="Shell command used to compile latex.").tag(config=True)

t = T()
reveal_type(Dict("foo")) # R: traitlets.traitlets.Dict
reveal_type(Dict("").tag(sync=True)) # R: traitlets.traitlets.Dict
reveal_type(Dict(None, allow_none=True)) # R: traitlets.traitlets.Dict
reveal_type(Dict(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.Dict
reveal_type(T.foo) # R: traitlets.traitlets.Dict
reveal_type(t.foo) # R: builtins.dict[Any, Any]


@pytest.mark.mypy_testing
def mypy_unicode_typing():
class T(HasTraits):
export_format = Unicode(
allow_none=False,
help="""The export format to be used, either one of the built-in formats
or a dotted object name that represents the import path for an
``Exporter`` class""",
).tag(config=True)

t = T()
reveal_type(
Unicode( # R: traitlets.traitlets.Unicode[builtins.str, Union[builtins.str, builtins.bytes]]
"foo"
)
)
reveal_type(
Unicode( # R: traitlets.traitlets.Unicode[builtins.str, Union[builtins.str, builtins.bytes]]
""
).tag(
sync=True
)
)
reveal_type(
Unicode( # R: traitlets.traitlets.Unicode[Union[builtins.str, None], Union[builtins.str, builtins.bytes, None]]
None, allow_none=True
)
)
reveal_type(
Unicode( # R: traitlets.traitlets.Unicode[Union[builtins.str, None], Union[builtins.str, builtins.bytes, None]]
None, allow_none=True
).tag(
sync=True
)
)
reveal_type(
T.export_format # R: traitlets.traitlets.Unicode[builtins.str, Union[builtins.str, builtins.bytes]]
)
reveal_type(t.export_format) # R: builtins.str


@pytest.mark.mypy_testing
def mypy_set_typing():
class T(HasTraits):
remove_cell_tags = Set(
Unicode(),
default_value=[],
help=(
"Tags indicating which cells are to be removed,"
"matches tags in ``cell.metadata.tags``."
),
).tag(config=True)

safe_output_keys = Set(
config=True,
default_value={
"metadata", # Not a mimetype per-se, but expected and safe.
"text/plain",
"text/latex",
"application/json",
"image/png",
"image/jpeg",
},
help="Cell output mimetypes to render without modification",
)

t = T()
reveal_type(Set("foo")) # R: traitlets.traitlets.Set
reveal_type(Set("").tag(sync=True)) # R: traitlets.traitlets.Set
reveal_type(Set(None, allow_none=True)) # R: traitlets.traitlets.Set
reveal_type(Set(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.Set
reveal_type(T.remove_cell_tags) # R: traitlets.traitlets.Set
reveal_type(t.remove_cell_tags) # R: builtins.set[Any]
reveal_type(T.safe_output_keys) # R: traitlets.traitlets.Set
reveal_type(t.safe_output_keys) # R: builtins.set[Any]


@pytest.mark.mypy_testing
def mypy_any_typing():
class T(HasTraits):
attributes = Any(
config=True,
default_value={
"a": ["href", "title"],
"abbr": ["title"],
"acronym": ["title"],
},
help="Allowed HTML tag attributes",
)

t = T()
reveal_type(Any("foo")) # R: traitlets.traitlets.Any
reveal_type(Any("").tag(sync=True)) # R: traitlets.traitlets.Any
reveal_type(Any(None, allow_none=True)) # R: traitlets.traitlets.Any
reveal_type(Any(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.Any
reveal_type(T.attributes) # R: traitlets.traitlets.Any
reveal_type(t.attributes) # R: Any


@pytest.mark.mypy_testing
def mypy_bool_typing():
class T(HasTraits):
Expand Down
Loading