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

feat: trait typing #818

Merged
merged 34 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
26648c4
feat: trait typing
vidartf Oct 20, 2022
9f6ab5c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Dec 20, 2022
7ec7565
pin pytest
blink1073 Dec 20, 2022
83555ff
fix version pin
blink1073 Dec 20, 2022
2c27a58
implement TCPAddress, Int, CInt and Bool
maartenbreddels Dec 20, 2022
48f8580
fix dependencies
maartenbreddels Dec 21, 2022
1f1ec19
black+mypy_testing issues
maartenbreddels Dec 21, 2022
b580a26
fix dependencies
maartenbreddels Dec 21, 2022
6cac256
type -> t.Type
maartenbreddels Dec 21, 2022
cce7513
backward compatible typing
maartenbreddels Dec 21, 2022
001f38a
overload init, not new
maartenbreddels Dec 21, 2022
bd613aa
use Literal from typing extension (py37 support)
maartenbreddels Dec 21, 2022
0b039a1
overload init, not new for bool
maartenbreddels Dec 21, 2022
2a85a13
more typing and lint issues
maartenbreddels Dec 21, 2022
3175a56
small fixes
maartenbreddels Dec 21, 2022
e4060b8
remove import that does not exist
maartenbreddels Dec 21, 2022
e45e1e1
restore application.py to main
maartenbreddels Dec 21, 2022
d4f5c80
fix application and instance
maartenbreddels Dec 21, 2022
d91339a
revert type:ignore
maartenbreddels Dec 21, 2022
4840eeb
no need for type:ignore[assignment]?
maartenbreddels Dec 21, 2022
637b524
implement comments by Ryan
maartenbreddels Dec 22, 2022
b158fb0
more typing
blink1073 Dec 24, 2022
4b85f72
remove runtime changes
blink1073 Dec 24, 2022
ab6f490
fixups
blink1073 Dec 24, 2022
6ade871
docstring
blink1073 Dec 24, 2022
807fa7b
lint
blink1073 Dec 24, 2022
b0a7ab1
Merge branch 'main' into trait-typing-take-2
blink1073 Sep 11, 2023
d4c41ec
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Sep 11, 2023
307419d
lint
blink1073 Sep 11, 2023
41823c7
lint
blink1073 Sep 11, 2023
ca52fe7
lint
blink1073 Sep 11, 2023
c5e420e
update types
blink1073 Sep 11, 2023
3026da1
update types
blink1073 Sep 11, 2023
6908f60
fix docs
blink1073 Sep 11, 2023
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
1 change: 1 addition & 0 deletions examples/myapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ def start(self):
print("app.config:")
print(self.config)
print("try running with --help-all to see all available flags")
assert self.log is not None
self.log.debug("Debug Message")
self.log.info("Info Message")
self.log.warning("Warning Message")
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ requires-python = ">=3.7"
dynamic = ["version"]

[project.optional-dependencies]
test = ["pytest", "pytest-mock", "pre-commit", "argcomplete>=2.0"]
test = ["pytest>=7.0,<7.2", "pytest-mock", "pre-commit", "argcomplete>=2.0", "pytest-mypy-testing"]
docs = [
"myst-parser",
"pydata-sphinx-theme",
Expand Down
20 changes: 12 additions & 8 deletions traitlets/config/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from copy import deepcopy
from logging.config import dictConfig
from textwrap import dedent
from typing import Any, Callable, TypeVar, cast
from typing import Callable, TypeVar, cast

from traitlets.config.configurable import Configurable, SingletonConfigurable
from traitlets.config.loader import (
Expand All @@ -29,6 +29,7 @@
PyFileConfigLoader,
)
from traitlets.traitlets import (
Any,
Bool,
Dict,
Enum,
Expand Down Expand Up @@ -151,6 +152,8 @@ class Application(SingletonConfigurable):
# line application
name: t.Union[str, Unicode] = Unicode("application")

log = Any(help="Logger or LoggerAdapter instance", allow_none=False)

# The description of the application that is printed at the beginning
# of the help.
description: t.Union[str, Unicode] = Unicode("This is an application.")
Expand Down Expand Up @@ -201,19 +204,19 @@ def _classes_inc_parents(self, classes=None):
)

# The log level for the application
log_level: t.Union[str, int, Enum] = Enum(
log_level: t.Union[str, int, Enum] = Enum( # type:ignore[assignment]
(0, 10, 20, 30, 40, 50, "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL"),
default_value=logging.WARN,
help="Set the log level by value or name.",
).tag(config=True)

_log_formatter_cls = LevelFormatter

log_datefmt: t.Union[str, Unicode] = Unicode(
log_datefmt: t.Union[str, Unicode] = Unicode( # type:ignore[assignment]
"%Y-%m-%d %H:%M:%S", help="The date format used by logging formatters for %(asctime)s"
).tag(config=True)

log_format: t.Union[str, Unicode] = Unicode(
log_format: t.Union[str, Unicode] = Unicode( # type:ignore[assignment]
"[%(name)s]%(highlevel)s %(message)s",
help="The Logging format template",
).tag(config=True)
Expand All @@ -237,7 +240,7 @@ def get_default_logging_config(self):
"console": {
"class": "logging.StreamHandler",
"formatter": "console",
"level": logging.getLevelName(self.log_level),
"level": logging.getLevelName(self.log_level), # type:ignore[arg-type]
"stream": "ext://sys.stderr",
},
},
Expand Down Expand Up @@ -402,7 +405,7 @@ def _log_default(self):
# and the second being the help string for the subcommand
subcommands: t.Union[t.Dict[str, t.Tuple[t.Any, str]], Dict] = Dict()
# parse_command_line will initialize a subapp, if requested
subapp = Instance("traitlets.config.application.Application", allow_none=True)
subapp: Instance[t.Any] = Instance("traitlets.config.application.Application", allow_none=True)

# extra command-line arguments that don't set config values
extra_args: t.Union[t.List[str], List] = List(Unicode())
Expand All @@ -420,11 +423,11 @@ def _log_default(self):

_loaded_config_files = List()

show_config: t.Union[bool, Bool] = Bool(
show_config: t.Union[bool, Bool] = Bool( # type:ignore[assignment]
help="Instead of starting the Application, dump configuration to stdout"
).tag(config=True)

show_config_json: t.Union[bool, Bool] = Bool(
show_config_json: t.Union[bool, Bool] = Bool( # type:ignore[assignment]
help="Instead of starting the Application, dump configuration to stdout (as JSON)"
).tag(config=True)

Expand Down Expand Up @@ -563,6 +566,7 @@ def emit_flag_help(self):
return

for flags, (cfg, fhelp) in self.flags.items():
assert isinstance(cfg, dict)
try:
if not isinstance(flags, tuple):
flags = (flags,)
Expand Down
9 changes: 7 additions & 2 deletions 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
import warnings
from copy import deepcopy
from textwrap import dedent
Expand Down Expand Up @@ -44,8 +45,10 @@ class MultipleInstanceError(ConfigurableError):

class Configurable(HasTraits):

config = Instance(Config, (), {})
parent = Instance("traitlets.config.configurable.Configurable", allow_none=True)
config: Instance[Config] = Instance(Config, (), {})
parent: Instance[t.Any] = Instance(
"traitlets.config.configurable.Configurable", allow_none=True
)

def __init__(self, **kwargs):
"""Create a configurable given a config config.
Expand Down Expand Up @@ -183,6 +186,7 @@ def _load_config(self, cfg, section_names=None, traits=None):
from difflib import get_close_matches

if isinstance(self, LoggingConfigurable):
assert self.log is not None
warn = self.log.warning
else:
warn = lambda msg: warnings.warn(msg, stacklevel=9) # noqa[E371]
Expand Down Expand Up @@ -460,6 +464,7 @@ def _validate_log(self, proposal):
@default("log")
def _log_default(self):
if isinstance(self.parent, LoggingConfigurable):
assert self.parent is not None
return self.parent.log
from traitlets import log

Expand Down
6 changes: 3 additions & 3 deletions traitlets/config/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ class LazyConfigValue(HasTraits):
_value = None

# list methods
_extend = List()
_prepend = List()
_inserts = List()
_extend: List = List()
_prepend: List = List()
_inserts: List = List()

def append(self, obj):
"""Append an item to a List"""
Expand Down
120 changes: 120 additions & 0 deletions traitlets/tests/test_typing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
from typing import Optional

import pytest
from typing_extensions import reveal_type

from traitlets import Bool, CInt, HasTraits, Int, TCPAddress


@pytest.mark.mypy_testing
def mypy_bool_typing():
class T(HasTraits):
b = Bool(True).tag(sync=True)
ob = Bool(None, allow_none=True).tag(sync=True)

t = T()
reveal_type(
Bool(True)
) # R: traitlets.traitlets.Bool[builtins.bool, Union[builtins.bool, builtins.int]]
reveal_type(
Bool(True).tag(sync=True)
) # R: traitlets.traitlets.Bool[builtins.bool, Union[builtins.bool, builtins.int]]
reveal_type(
Bool(None, allow_none=True)
) # R: traitlets.traitlets.Bool[Union[builtins.bool, None], Union[builtins.bool, builtins.int, None]]
reveal_type(
Bool(None, allow_none=True).tag(sync=True)
) # R: traitlets.traitlets.Bool[Union[builtins.bool, None], Union[builtins.bool, builtins.int, None]]
reveal_type(
T.b
) # R: traitlets.traitlets.Bool[builtins.bool, Union[builtins.bool, builtins.int]]
reveal_type(t.b) # R: builtins.bool
reveal_type(t.ob) # R: Union[builtins.bool, None]
reveal_type(
T.b
) # R: traitlets.traitlets.Bool[builtins.bool, Union[builtins.bool, builtins.int]]
reveal_type(
T.ob
) # R: traitlets.traitlets.Bool[Union[builtins.bool, None], Union[builtins.bool, builtins.int, None]]
# we would expect this to be Optional[Union[bool, int]], but...
t.b = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "Union[bool, int]") [assignment]
t.b = None # E: Incompatible types in assignment (expression has type "None", variable has type "Union[bool, int]") [assignment]


@pytest.mark.mypy_testing
def mypy_int_typing():
class T(HasTraits):
i: Int[int, int] = Int(42).tag(sync=True)
oi: Int[Optional[int], Optional[int]] = Int(42, allow_none=True).tag(sync=True)

t = T()
reveal_type(Int(True)) # R: traitlets.traitlets.Int[builtins.int, builtins.int]
reveal_type(Int(True).tag(sync=True)) # R: traitlets.traitlets.Int[builtins.int, builtins.int]
reveal_type(
Int(None, allow_none=True)
) # R: traitlets.traitlets.Int[Union[builtins.int, None], Union[builtins.int, None]]
reveal_type(
Int(None, allow_none=True).tag(sync=True)
) # R: traitlets.traitlets.Int[Union[builtins.int, None], Union[builtins.int, None]]
reveal_type(T.i) # R: traitlets.traitlets.Int[builtins.int, builtins.int]
reveal_type(t.i) # R: builtins.int
reveal_type(t.oi) # R: Union[builtins.int, None]
reveal_type(T.i) # R: traitlets.traitlets.Int[builtins.int, builtins.int]
reveal_type(
T.oi
) # R: traitlets.traitlets.Int[Union[builtins.int, None], Union[builtins.int, None]]
t.i = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "int") [assignment]
t.i = None # E: Incompatible types in assignment (expression has type "None", variable has type "int") [assignment]
t.i = 1.2 # E: Incompatible types in assignment (expression has type "float", variable has type "int") [assignment]


@pytest.mark.mypy_testing
def mypy_cint_typing():
class T(HasTraits):
i = CInt(42).tag(sync=True)
oi = CInt(42, allow_none=True).tag(sync=True)

t = T()
reveal_type(CInt(True)) # R: traitlets.traitlets.CInt[builtins.int, Union[builtins.int, Any]]
reveal_type(
CInt(True).tag(sync=True)
) # R: traitlets.traitlets.CInt[builtins.int, Union[builtins.int, Any]]
reveal_type(
CInt(None, allow_none=True)
) # R: traitlets.traitlets.CInt[Union[builtins.int, None], Union[builtins.int, Any, None]]
reveal_type(
CInt(None, allow_none=True).tag(sync=True)
) # R: traitlets.traitlets.CInt[Union[builtins.int, None], Union[builtins.int, Any, None]]
reveal_type(T.i) # R: traitlets.traitlets.CInt[builtins.int, Union[builtins.int, Any]]
reveal_type(t.i) # R: builtins.int
reveal_type(t.oi) # R: Union[builtins.int, None]
reveal_type(T.i) # R: traitlets.traitlets.CInt[builtins.int, Union[builtins.int, Any]]
reveal_type(
T.oi
) # R: traitlets.traitlets.CInt[Union[builtins.int, None], Union[builtins.int, Any, None]]


@pytest.mark.mypy_testing
def mypy_tcp_typing():
class T(HasTraits):
tcp = TCPAddress()
otcp = TCPAddress(None, allow_none=True)

t = T()
reveal_type(t.tcp) # R: Tuple[builtins.str, builtins.int]
reveal_type(
T.tcp
) # R: traitlets.traitlets.TCPAddress[Tuple[builtins.str, builtins.int], Tuple[builtins.str, builtins.int]]
reveal_type(
T.tcp.tag(sync=True)
) # R:traitlets.traitlets.TCPAddress[Tuple[builtins.str, builtins.int], Tuple[builtins.str, builtins.int]]
reveal_type(t.otcp) # R: Union[Tuple[builtins.str, builtins.int], None]
reveal_type(
T.otcp
) # R: traitlets.traitlets.TCPAddress[Union[Tuple[builtins.str, builtins.int], None], Union[Tuple[builtins.str, builtins.int], None]]
reveal_type(
T.otcp.tag(sync=True)
) # R: traitlets.traitlets.TCPAddress[Union[Tuple[builtins.str, builtins.int], None], Union[Tuple[builtins.str, builtins.int], None]]
t.tcp = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "Tuple[str, int]") [assignment]
t.otcp = "foo" # E: Incompatible types in assignment (expression has type "str", variable has type "Optional[Tuple[str, int]]") [assignment]
t.tcp = None # E: Incompatible types in assignment (expression has type "None", variable has type "Tuple[str, int]") [assignment]
Loading