Skip to content

Commit c0a3afd

Browse files
authored
Typing upgrades (#868)
1 parent 2ce600f commit c0a3afd

File tree

10 files changed

+474
-131
lines changed

10 files changed

+474
-131
lines changed

traitlets/config/application.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -446,7 +446,7 @@ def _show_config_changed(self, change):
446446
self._save_start = self.start
447447
self.start = self.start_show_config # type:ignore[method-assign]
448448

449-
def __init__(self, **kwargs):
449+
def __init__(self, **kwargs: t.Any) -> None:
450450
SingletonConfigurable.__init__(self, **kwargs)
451451
# Ensure my class is in self.classes, so my attributes appear in command line
452452
# options and config files.

traitlets/config/configurable.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66

77
import logging
8+
import typing as t
89
from copy import deepcopy
910
from textwrap import dedent
1011

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

49-
def __init__(self, **kwargs):
50+
def __init__(self, **kwargs: t.Any) -> None:
5051
"""Create a configurable given a config config.
5152
5253
Parameters

traitlets/config/loader.py

Lines changed: 18 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

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

67
import argparse
78
import copy
@@ -236,7 +237,7 @@ class Config(dict): # type:ignore[type-arg]
236237
237238
"""
238239

239-
def __init__(self, *args, **kwds):
240+
def __init__(self, *args: t.Any, **kwds: t.Any) -> None:
240241
dict.__init__(self, *args, **kwds)
241242
self._ensure_subconfig()
242243

@@ -273,15 +274,15 @@ def merge(self, other):
273274

274275
self.update(to_update)
275276

276-
def collisions(self, other: "Config") -> t.Dict[str, t.Any]:
277+
def collisions(self, other: Config) -> dict[str, t.Any]:
277278
"""Check for collisions between two config objects.
278279
279280
Returns a dict of the form {"Class": {"trait": "collision message"}}`,
280281
indicating which values have been ignored.
281282
282283
An empty dict indicates no collisions.
283284
"""
284-
collisions: t.Dict[str, t.Any] = {}
285+
collisions: dict[str, t.Any] = {}
285286
for section in self:
286287
if section not in other:
287288
continue
@@ -490,7 +491,7 @@ def _log_default(self):
490491

491492
return get_logger()
492493

493-
def __init__(self, log=None):
494+
def __init__(self, log: t.Any = None) -> None:
494495
"""A base class for config loaders.
495496
496497
log : instance of :class:`logging.Logger` to use.
@@ -532,7 +533,7 @@ class FileConfigLoader(ConfigLoader):
532533
here.
533534
"""
534535

535-
def __init__(self, filename, path=None, **kw):
536+
def __init__(self, filename: str, path: str | None = None, **kw: t.Any) -> None:
536537
"""Build a config loader for a filename and path.
537538
538539
Parameters
@@ -795,12 +796,12 @@ class ArgParseConfigLoader(CommandLineConfigLoader):
795796

796797
def __init__(
797798
self,
798-
argv: t.Optional[t.List[str]] = None,
799-
aliases: t.Optional[t.Dict[Flags, str]] = None,
800-
flags: t.Optional[t.Dict[Flags, str]] = None,
799+
argv: list[str] | None = None,
800+
aliases: dict[Flags, str] | None = None,
801+
flags: dict[Flags, str] | None = None,
801802
log: t.Any = None,
802-
classes: t.Optional[t.List[t.Type[t.Any]]] = None,
803-
subcommands: t.Optional[SubcommandsDict] = None,
803+
classes: list[type[t.Any]] | None = None,
804+
subcommands: SubcommandsDict | None = None,
804805
*parser_args: t.Any,
805806
**parser_kw: t.Any,
806807
) -> None:
@@ -899,17 +900,15 @@ def _create_parser(self):
899900
def _add_arguments(self, aliases, flags, classes):
900901
raise NotImplementedError("subclasses must implement _add_arguments")
901902

902-
def _argcomplete(
903-
self, classes: t.List[t.Any], subcommands: t.Optional[SubcommandsDict]
904-
) -> None:
903+
def _argcomplete(self, classes: list[t.Any], subcommands: SubcommandsDict | None) -> None:
905904
"""If argcomplete is enabled, allow triggering command-line autocompletion"""
906905
pass
907906

908907
def _parse_args(self, args):
909908
"""self.parser->self.parsed_data"""
910909
uargs = [cast_unicode(a) for a in args]
911910

912-
unpacked_aliases: t.Dict[str, str] = {}
911+
unpacked_aliases: dict[str, str] = {}
913912
if self.aliases:
914913
unpacked_aliases = {}
915914
for alias, alias_target in self.aliases.items():
@@ -957,7 +956,7 @@ def _convert_to_config(self):
957956
class _FlagAction(argparse.Action):
958957
"""ArgParse action to handle a flag"""
959958

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

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

1111-
def _argcomplete(
1112-
self, classes: t.List[t.Any], subcommands: t.Optional[SubcommandsDict]
1113-
) -> None:
1110+
def _argcomplete(self, classes: list[t.Any], subcommands: SubcommandsDict | None) -> None:
11141111
"""If argcomplete is enabled, allow triggering command-line autocompletion"""
11151112
try:
11161113
import argcomplete # noqa
@@ -1132,7 +1129,7 @@ class KeyValueConfigLoader(KVArgParseConfigLoader):
11321129
Use KVArgParseConfigLoader
11331130
"""
11341131

1135-
def __init__(self, *args, **kwargs):
1132+
def __init__(self, *args: t.Any, **kwargs: t.Any) -> None:
11361133
warnings.warn(
11371134
"KeyValueConfigLoader is deprecated since Traitlets 5.0."
11381135
" Use KVArgParseConfigLoader instead.",

traitlets/log.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,14 @@
22

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

67
import logging
78

8-
_logger = None
9+
_logger: logging.Logger | None = None
910

1011

11-
def get_logger():
12+
def get_logger() -> logging.Logger:
1213
"""Grab the global logger instance.
1314
1415
If a global Application is instantiated, grab its logger.

traitlets/tests/test_typing.py

Lines changed: 202 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,220 @@
44

55
import pytest
66

7-
from traitlets import Bool, CInt, HasTraits, Instance, Int, TCPAddress
7+
from traitlets import (
8+
Any,
9+
Bool,
10+
CInt,
11+
Dict,
12+
HasTraits,
13+
Instance,
14+
Int,
15+
List,
16+
Set,
17+
TCPAddress,
18+
Type,
19+
Unicode,
20+
Union,
21+
default,
22+
observe,
23+
validate,
24+
)
25+
from traitlets.config import Config
826

927
if not typing.TYPE_CHECKING:
1028

1129
def reveal_type(*args, **kwargs):
1230
pass
1331

1432

33+
# mypy: disallow-untyped-calls
34+
35+
1536
class Foo:
1637
def __init__(self, c):
1738
self.c = c
1839

1940

41+
@pytest.mark.mypy_testing
42+
def mypy_decorator_typing():
43+
class T(HasTraits):
44+
foo = Unicode("").tag(config=True)
45+
46+
@default("foo")
47+
def _default_foo(self) -> str:
48+
return "hi"
49+
50+
@observe("foo")
51+
def _foo_observer(self, change: typing.Any) -> bool:
52+
return True
53+
54+
@validate("foo")
55+
def _foo_validate(self, commit: typing.Any) -> bool:
56+
return True
57+
58+
t = T()
59+
reveal_type(t.foo) # R: builtins.str
60+
reveal_type(t._foo_observer) # R: Any
61+
reveal_type(t._foo_validate) # R: Any
62+
63+
64+
@pytest.mark.mypy_testing
65+
def mypy_config_typing():
66+
c = Config(
67+
{
68+
"ExtractOutputPreprocessor": {"enabled": True},
69+
}
70+
)
71+
reveal_type(c) # R: traitlets.config.loader.Config
72+
73+
74+
@pytest.mark.mypy_testing
75+
def mypy_union_typing():
76+
class T(HasTraits):
77+
style = Union(
78+
[Unicode("default"), Type(klass=object)],
79+
help="Name of the pygments style to use",
80+
default_value="hi",
81+
).tag(config=True)
82+
83+
t = T()
84+
reveal_type(Union("foo")) # R: traitlets.traitlets.Union
85+
reveal_type(Union("").tag(sync=True)) # R: traitlets.traitlets.Union
86+
reveal_type(Union(None, allow_none=True)) # R: traitlets.traitlets.Union
87+
reveal_type(Union(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.Union
88+
reveal_type(T.style) # R: traitlets.traitlets.Union
89+
reveal_type(t.style) # R: Any
90+
91+
92+
@pytest.mark.mypy_testing
93+
def mypy_list_typing():
94+
class T(HasTraits):
95+
latex_command = List(
96+
["xelatex", "{filename}", "-quiet"], help="Shell command used to compile latex."
97+
).tag(config=True)
98+
99+
t = T()
100+
reveal_type(List("foo")) # R: traitlets.traitlets.List
101+
reveal_type(List("").tag(sync=True)) # R: traitlets.traitlets.List
102+
reveal_type(List(None, allow_none=True)) # R: traitlets.traitlets.List
103+
reveal_type(List(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.List
104+
reveal_type(T.latex_command) # R: traitlets.traitlets.List
105+
reveal_type(t.latex_command) # R: builtins.list[Any]
106+
107+
108+
@pytest.mark.mypy_testing
109+
def mypy_dict_typing():
110+
class T(HasTraits):
111+
foo = Dict({}, help="Shell command used to compile latex.").tag(config=True)
112+
113+
t = T()
114+
reveal_type(Dict("foo")) # R: traitlets.traitlets.Dict
115+
reveal_type(Dict("").tag(sync=True)) # R: traitlets.traitlets.Dict
116+
reveal_type(Dict(None, allow_none=True)) # R: traitlets.traitlets.Dict
117+
reveal_type(Dict(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.Dict
118+
reveal_type(T.foo) # R: traitlets.traitlets.Dict
119+
reveal_type(t.foo) # R: builtins.dict[Any, Any]
120+
121+
122+
@pytest.mark.mypy_testing
123+
def mypy_unicode_typing():
124+
class T(HasTraits):
125+
export_format = Unicode(
126+
allow_none=False,
127+
help="""The export format to be used, either one of the built-in formats
128+
or a dotted object name that represents the import path for an
129+
``Exporter`` class""",
130+
).tag(config=True)
131+
132+
t = T()
133+
reveal_type(
134+
Unicode( # R: traitlets.traitlets.Unicode[builtins.str, Union[builtins.str, builtins.bytes]]
135+
"foo"
136+
)
137+
)
138+
reveal_type(
139+
Unicode( # R: traitlets.traitlets.Unicode[builtins.str, Union[builtins.str, builtins.bytes]]
140+
""
141+
).tag(
142+
sync=True
143+
)
144+
)
145+
reveal_type(
146+
Unicode( # R: traitlets.traitlets.Unicode[Union[builtins.str, None], Union[builtins.str, builtins.bytes, None]]
147+
None, allow_none=True
148+
)
149+
)
150+
reveal_type(
151+
Unicode( # R: traitlets.traitlets.Unicode[Union[builtins.str, None], Union[builtins.str, builtins.bytes, None]]
152+
None, allow_none=True
153+
).tag(
154+
sync=True
155+
)
156+
)
157+
reveal_type(
158+
T.export_format # R: traitlets.traitlets.Unicode[builtins.str, Union[builtins.str, builtins.bytes]]
159+
)
160+
reveal_type(t.export_format) # R: builtins.str
161+
162+
163+
@pytest.mark.mypy_testing
164+
def mypy_set_typing():
165+
class T(HasTraits):
166+
remove_cell_tags = Set(
167+
Unicode(),
168+
default_value=[],
169+
help=(
170+
"Tags indicating which cells are to be removed,"
171+
"matches tags in ``cell.metadata.tags``."
172+
),
173+
).tag(config=True)
174+
175+
safe_output_keys = Set(
176+
config=True,
177+
default_value={
178+
"metadata", # Not a mimetype per-se, but expected and safe.
179+
"text/plain",
180+
"text/latex",
181+
"application/json",
182+
"image/png",
183+
"image/jpeg",
184+
},
185+
help="Cell output mimetypes to render without modification",
186+
)
187+
188+
t = T()
189+
reveal_type(Set("foo")) # R: traitlets.traitlets.Set
190+
reveal_type(Set("").tag(sync=True)) # R: traitlets.traitlets.Set
191+
reveal_type(Set(None, allow_none=True)) # R: traitlets.traitlets.Set
192+
reveal_type(Set(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.Set
193+
reveal_type(T.remove_cell_tags) # R: traitlets.traitlets.Set
194+
reveal_type(t.remove_cell_tags) # R: builtins.set[Any]
195+
reveal_type(T.safe_output_keys) # R: traitlets.traitlets.Set
196+
reveal_type(t.safe_output_keys) # R: builtins.set[Any]
197+
198+
199+
@pytest.mark.mypy_testing
200+
def mypy_any_typing():
201+
class T(HasTraits):
202+
attributes = Any(
203+
config=True,
204+
default_value={
205+
"a": ["href", "title"],
206+
"abbr": ["title"],
207+
"acronym": ["title"],
208+
},
209+
help="Allowed HTML tag attributes",
210+
)
211+
212+
t = T()
213+
reveal_type(Any("foo")) # R: traitlets.traitlets.Any
214+
reveal_type(Any("").tag(sync=True)) # R: traitlets.traitlets.Any
215+
reveal_type(Any(None, allow_none=True)) # R: traitlets.traitlets.Any
216+
reveal_type(Any(None, allow_none=True).tag(sync=True)) # R: traitlets.traitlets.Any
217+
reveal_type(T.attributes) # R: traitlets.traitlets.Any
218+
reveal_type(t.attributes) # R: Any
219+
220+
20221
@pytest.mark.mypy_testing
21222
def mypy_bool_typing():
22223
class T(HasTraits):

0 commit comments

Comments
 (0)