Skip to content

Commit aa1081b

Browse files
maartenbreddelsvidartfblink1073pre-commit-ci[bot]
authored
feat: trait typing (#818)
Co-authored-by: Vidar Tonaas Fauske <510760+vidartf@users.noreply.github.com> Co-authored-by: Steven Silvester <steven.silvester@ieee.org> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent b1cdea4 commit aa1081b

File tree

10 files changed

+864
-130
lines changed

10 files changed

+864
-130
lines changed

docs/source/conf.py

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,13 @@
2222
HERE = osp.abspath(osp.dirname(__file__))
2323
ROOT = osp.dirname(osp.dirname(HERE))
2424

25+
from traitlets import version_info
26+
2527
# If extensions (or modules to document with autodoc) are in another directory,
2628
# add these directories to sys.path here. If the directory is relative to the
2729
# documentation root, use os.path.abspath to make it absolute, like shown here.
2830
# sys.path.insert(0, os.path.abspath('.'))
2931

30-
# We load the ipython release info into a dict by explicit execution
31-
_release = {} # type:ignore
32-
exec( # noqa
33-
compile(
34-
open(osp.join(ROOT, "traitlets/_version.py")).read(),
35-
"../../traitlets/_version.py",
36-
"exec",
37-
),
38-
_release,
39-
)
40-
4132
# -- General configuration ------------------------------------------------
4233

4334
# If your documentation needs a minimal Sphinx version, state it here.
@@ -64,7 +55,7 @@
6455
source_suffix = ".rst"
6556

6657
# Add dev disclaimer.
67-
if _release["version_info"][-1] == "dev":
58+
if version_info[-1] == "dev":
6859
rst_prolog = """
6960
.. note::
7061
@@ -89,9 +80,9 @@
8980
# built documents.
9081
#
9182
# The short X.Y version.
92-
version = ".".join(map(str, _release["version_info"][:2]))
83+
version = ".".join(map(str, version_info[:2]))
9384
# The full version, including alpha/beta/rc tags.
94-
release = _release["__version__"]
85+
release = "__version__"
9586

9687
# The language for content autogenerated by Sphinx. Refer to documentation
9788
# for a list of supported languages.

examples/docs/from_string.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class App(Application):
2525
)
2626

2727
def start(self):
28-
print(f"key={self.key}")
28+
print(f"key={self.key.decode('utf8')}")
2929

3030

3131
if __name__ == "__main__":

examples/myapp.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ def start(self):
9494
print("app.config:")
9595
print(self.config)
9696
print("try running with --help-all to see all available flags")
97+
assert self.log is not None
9798
self.log.debug("Debug Message")
9899
self.log.info("Info Message")
99100
self.log.warning("Warning Message")

pyproject.toml

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,10 @@ classifiers = [
2020
urls = {Homepage = "https://github.com/ipython/traitlets"}
2121
requires-python = ">=3.7"
2222
dynamic = ["version"]
23+
dependencies = ["typing_extensions>=4.0.1"]
2324

2425
[project.optional-dependencies]
25-
test = ["pytest", "pytest-mock", "pre-commit", "argcomplete>=2.0"]
26+
test = ["pytest>=7.0,<7.2", "pytest-mock", "pre-commit", "argcomplete>=2.0", "pytest-mypy-testing", "mypy @ git+https://github.com/python/mypy.git@cb1d1a0baba37f35268cb605b7345726f257f960#egg=mypy"]
2627
docs = [
2728
"myst-parser",
2829
"pydata-sphinx-theme",
@@ -32,6 +33,9 @@ docs = [
3233
[tool.hatch.version]
3334
path = "traitlets/_version.py"
3435

36+
[tool.hatch.metadata]
37+
allow-direct-references = true
38+
3539
[tool.hatch.envs.docs]
3640
features = ["docs"]
3741
[tool.hatch.envs.docs.scripts]
@@ -52,7 +56,7 @@ nowarn = "test -W default {args}"
5256

5357
[tool.hatch.envs.typing]
5458
features = ["test"]
55-
dependencies = ["mypy>=0.990"]
59+
dependencies = ["mypy @ git+https://github.com/python/mypy.git@cb1d1a0baba37f35268cb605b7345726f257f960#egg=mypy"]
5660
[tool.hatch.envs.typing.scripts]
5761
test = "mypy --install-types --non-interactive {args:.}"
5862

@@ -89,7 +93,7 @@ warn_unused_configs = true
8993
warn_redundant_casts = true
9094
warn_return_any = true
9195
warn_unused_ignores = true
92-
exclude = ["examples/docs/configs"]
96+
exclude = ["examples/docs/configs", "traitlets/tests/test_typing.py"]
9397

9498
[tool.pytest.ini_options]
9599
addopts = "--durations=10 -ra --showlocals --doctest-modules --color yes --ignore examples/docs/configs"

traitlets/config/application.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -149,21 +149,29 @@ class Application(SingletonConfigurable):
149149

150150
# The name of the application, will usually match the name of the command
151151
# line application
152-
name: t.Union[str, Unicode] = Unicode("application")
152+
name: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode("application")
153153

154154
# The description of the application that is printed at the beginning
155155
# of the help.
156-
description: t.Union[str, Unicode] = Unicode("This is an application.")
156+
description: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode(
157+
"This is an application."
158+
)
157159
# default section descriptions
158-
option_description: t.Union[str, Unicode] = Unicode(option_description)
159-
keyvalue_description: t.Union[str, Unicode] = Unicode(keyvalue_description)
160-
subcommand_description: t.Union[str, Unicode] = Unicode(subcommand_description)
160+
option_description: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode(
161+
option_description
162+
)
163+
keyvalue_description: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode(
164+
keyvalue_description
165+
)
166+
subcommand_description: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode(
167+
subcommand_description
168+
)
161169

162170
python_config_loader_class = PyFileConfigLoader
163171
json_config_loader_class = JSONFileConfigLoader
164172

165173
# The usage and example string that goes at the end of the help string.
166-
examples: t.Union[str, Unicode] = Unicode()
174+
examples: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode()
167175

168176
# A sequence of Configurable subclasses whose config=True attributes will
169177
# be exposed at the command line.
@@ -190,30 +198,30 @@ def _classes_inc_parents(self, classes=None):
190198
yield parent
191199

192200
# The version string of this application.
193-
version: t.Union[str, Unicode] = Unicode("0.0")
201+
version: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode("0.0")
194202

195203
# the argv used to initialize the application
196204
argv: t.Union[t.List[str], List] = List()
197205

198206
# Whether failing to load config files should prevent startup
199-
raise_config_file_errors: t.Union[bool, Bool] = Bool(
207+
raise_config_file_errors: t.Union[bool, Bool[bool, t.Union[bool, int]]] = Bool(
200208
TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR
201209
)
202210

203211
# The log level for the application
204-
log_level: t.Union[str, int, Enum] = Enum(
212+
log_level: t.Union[str, int, Enum[t.Any, t.Any]] = Enum(
205213
(0, 10, 20, 30, 40, 50, "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL"),
206214
default_value=logging.WARN,
207215
help="Set the log level by value or name.",
208216
).tag(config=True)
209217

210218
_log_formatter_cls = LevelFormatter
211219

212-
log_datefmt: t.Union[str, Unicode] = Unicode(
220+
log_datefmt: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode(
213221
"%Y-%m-%d %H:%M:%S", help="The date format used by logging formatters for %(asctime)s"
214222
).tag(config=True)
215223

216-
log_format: t.Union[str, Unicode] = Unicode(
224+
log_format: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode(
217225
"[%(name)s]%(highlevel)s %(message)s",
218226
help="The Logging format template",
219227
).tag(config=True)
@@ -420,11 +428,11 @@ def _log_default(self):
420428

421429
_loaded_config_files = List()
422430

423-
show_config: t.Union[bool, Bool] = Bool(
431+
show_config: t.Union[bool, Bool[bool, t.Union[bool, int]]] = Bool(
424432
help="Instead of starting the Application, dump configuration to stdout"
425433
).tag(config=True)
426434

427-
show_config_json: t.Union[bool, Bool] = Bool(
435+
show_config_json: t.Union[bool, Bool[bool, t.Union[bool, int]]] = Bool(
428436
help="Instead of starting the Application, dump configuration to stdout (as JSON)"
429437
).tag(config=True)
430438

@@ -436,7 +444,7 @@ def _show_config_json_changed(self, change):
436444
def _show_config_changed(self, change):
437445
if change.new:
438446
self._save_start = self.start
439-
self.start = self.start_show_config # type:ignore[method-assign]
447+
self.start = self.start_show_config # type:ignore[assignment]
440448

441449
def __init__(self, **kwargs):
442450
SingletonConfigurable.__init__(self, **kwargs)

traitlets/config/configurable.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ def _load_config(self, cfg, section_names=None, traits=None):
182182
from difflib import get_close_matches
183183

184184
if isinstance(self, LoggingConfigurable):
185+
assert self.log is not None
185186
warn = self.log.warning
186187
else:
187188

@@ -462,6 +463,7 @@ def _validate_log(self, proposal):
462463
@default("log")
463464
def _log_default(self):
464465
if isinstance(self.parent, LoggingConfigurable):
466+
assert self.parent is not None
465467
return self.parent.log
466468
from traitlets import log
467469

traitlets/config/loader.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,9 @@ class LazyConfigValue(HasTraits):
9696
_value = None
9797

9898
# list methods
99-
_extend = List()
100-
_prepend = List()
101-
_inserts = List()
99+
_extend: List = List()
100+
_prepend: List = List()
101+
_inserts: List = List()
102102

103103
def append(self, obj):
104104
"""Append an item to a List"""

traitlets/tests/test_traitlets.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ class A(HasTraitsStub):
112112
self.assertEqual(a._notify_new, 10)
113113

114114
def test_validate(self):
115-
class MyTT(TraitType):
115+
class MyTT(TraitType[int, int]):
116116
def validate(self, inst, value):
117117
return -1
118118

@@ -127,7 +127,7 @@ class A(HasTraitsStub):
127127
self.assertEqual(a.tt, -1)
128128

129129
def test_default_validate(self):
130-
class MyIntTT(TraitType):
130+
class MyIntTT(TraitType[int, int]):
131131
def validate(self, obj, value):
132132
if isinstance(value, int):
133133
return value
@@ -154,7 +154,7 @@ class A(HasTraits):
154154

155155
def test_error(self):
156156
class A(HasTraits):
157-
tt = TraitType()
157+
tt = TraitType[int, int]()
158158

159159
a = A()
160160
self.assertRaises(TraitError, A.tt.error, a, 10)
@@ -270,14 +270,14 @@ def _default_x(self):
270270
self.assertEqual(a._trait_values, {"x": 11})
271271

272272
def test_tag_metadata(self):
273-
class MyIntTT(TraitType):
273+
class MyIntTT(TraitType[int, int]):
274274
metadata = {"a": 1, "b": 2}
275275

276276
a = MyIntTT(10).tag(b=3, c=4)
277277
self.assertEqual(a.metadata, {"a": 1, "b": 3, "c": 4})
278278

279279
def test_metadata_localized_instance(self):
280-
class MyIntTT(TraitType):
280+
class MyIntTT(TraitType[int, int]):
281281
metadata = {"a": 1, "b": 2}
282282

283283
a = MyIntTT(10)
@@ -325,7 +325,7 @@ class Foo(HasTraits):
325325
self.assertEqual(Foo().bar, {})
326326

327327
def test_deprecated_metadata_access(self):
328-
class MyIntTT(TraitType):
328+
class MyIntTT(TraitType[int, int]):
329329
metadata = {"a": 1, "b": 2}
330330

331331
a = MyIntTT(10)
@@ -394,12 +394,12 @@ class C(HasTraits):
394394

395395
def test_this_class(self):
396396
class A(HasTraits):
397-
t = This()
398-
tt = This()
397+
t = This["A"]()
398+
tt = This["A"]()
399399

400400
class B(A):
401-
tt = This()
402-
ttt = This()
401+
tt = This["A"]()
402+
ttt = This["A"]()
403403

404404
self.assertEqual(A.t.this_class, A)
405405
self.assertEqual(B.t.this_class, A)
@@ -1094,7 +1094,7 @@ class Bar(Foo):
10941094
class Bah:
10951095
pass
10961096

1097-
class FooInstance(Instance):
1097+
class FooInstance(Instance[Foo]):
10981098
klass = Foo
10991099

11001100
class A(HasTraits):
@@ -1170,15 +1170,15 @@ class Foo:
11701170

11711171
def inner():
11721172
class A(HasTraits):
1173-
inst = Instance(Foo())
1173+
inst = Instance(Foo()) # type:ignore
11741174

11751175
self.assertRaises(TraitError, inner)
11761176

11771177

11781178
class TestThis(TestCase):
11791179
def test_this_class(self):
11801180
class Foo(HasTraits):
1181-
this = This()
1181+
this = This["Foo"]()
11821182

11831183
f = Foo()
11841184
self.assertEqual(f.this, None)
@@ -1189,15 +1189,15 @@ class Foo(HasTraits):
11891189

11901190
def test_this_inst(self):
11911191
class Foo(HasTraits):
1192-
this = This()
1192+
this = This["Foo"]()
11931193

11941194
f = Foo()
11951195
f.this = Foo()
11961196
self.assertTrue(isinstance(f.this, Foo))
11971197

11981198
def test_subclass(self):
11991199
class Foo(HasTraits):
1200-
t = This()
1200+
t = This["Foo"]()
12011201

12021202
class Bar(Foo):
12031203
pass
@@ -1211,7 +1211,7 @@ class Bar(Foo):
12111211

12121212
def test_subclass_override(self):
12131213
class Foo(HasTraits):
1214-
t = This()
1214+
t = This["Foo"]()
12151215

12161216
class Bar(Foo):
12171217
t = This()
@@ -2423,11 +2423,11 @@ def test_notification_order():
24232423
# Traits for Forward Declaration Tests
24242424
###
24252425
class ForwardDeclaredInstanceTrait(HasTraits):
2426-
value = ForwardDeclaredInstance("ForwardDeclaredBar", allow_none=True)
2426+
value = ForwardDeclaredInstance["ForwardDeclaredBar"]("ForwardDeclaredBar", allow_none=True)
24272427

24282428

24292429
class ForwardDeclaredTypeTrait(HasTraits):
2430-
value = ForwardDeclaredType("ForwardDeclaredBar", allow_none=True)
2430+
value = ForwardDeclaredType[t.Any, t.Any]("ForwardDeclaredBar", allow_none=True)
24312431

24322432

24332433
class ForwardDeclaredInstanceListTrait(HasTraits):

0 commit comments

Comments
 (0)