Skip to content
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
14 changes: 14 additions & 0 deletions tests/test_traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1657,6 +1657,11 @@ def coerce(self, value):
return pathlib.Path(value)


def test_path_from_string():
assert Path().from_string("foo/bar") == pathlib.Path("foo/bar")
assert Path(allow_none=True).from_string("None") is None


class ListTrait(HasTraits):
value = List(Int())

Expand Down Expand Up @@ -3040,6 +3045,15 @@ def test_list_from_string(s, expected):
_from_string_test(List, s, expected)


def test_container_from_string_list_deprecated_literal():
# Passing a whole container literal as a single --opt="[...]" element is
# deprecated (traitlets 5.0) but still supported.
trait = List(Integer())
trait.name = "x"
with pytest.warns(DeprecationWarning, match="is deprecated in traitlets 5.0"):
assert trait.from_string_list(["[1, 2, 3]"]) == [1, 2, 3]


@pytest.mark.parametrize(
"s, expected, value_trait",
[
Expand Down
11 changes: 8 additions & 3 deletions traitlets/config/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,14 @@
from __future__ import annotations

import functools
import json
import logging
import os
import pprint
import re
import sys
import typing as t
from collections import OrderedDict, defaultdict
from contextlib import suppress
from copy import deepcopy
from logging.config import dictConfig
from textwrap import dedent

from traitlets.config.configurable import Configurable, SingletonConfigurable
Expand Down Expand Up @@ -286,6 +283,8 @@ def _observe_logging_default(self, change: Bunch) -> None:
self._configure_logging()

def _configure_logging(self) -> None:
from logging.config import dictConfig

config = self.get_default_logging_config()
nested_update(config, self.logging_config or {})
dictConfig(config)
Expand Down Expand Up @@ -483,6 +482,8 @@ def start_show_config(self) -> None:
cls_config.pop("show_config_json", None)

if self.show_config_json:
import json

json.dump(config, sys.stdout, indent=1, sort_keys=True, default=repr)
# add trailing newline
sys.stdout.write("\n")
Expand All @@ -498,6 +499,8 @@ def start_show_config(self) -> None:
class_config = config[classname]
if not class_config:
continue
import pprint

print(classname)
pformat_kwargs: StrDict = dict(indent=4, compact=True) # noqa: C408

Expand Down Expand Up @@ -929,6 +932,8 @@ def _load_config_files(
if log:
log.debug("Loaded config file: %s", loader.full_filename)
if config:
import json

for filename, earlier_config in zip(filenames, loaded, strict=True):
collisions = earlier_config.collisions(config)
if collisions and log:
Expand Down
5 changes: 4 additions & 1 deletion traitlets/config/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import argparse
import copy
import functools
import json
import os
import re
import sys
Expand Down Expand Up @@ -577,6 +576,8 @@ def load_config(self) -> Config:
return self.config

def _read_file_as_dict(self) -> dict[str, t.Any]:
import json

with open(self.full_filename) as f:
return t.cast("dict[str, t.Any]", json.load(f))

Expand All @@ -603,6 +604,8 @@ def __exit__(self, exc_type: object, exc_value: object, traceback: object) -> No
configuration to disk.
"""
self.config.version = 1
import json

json_config = json.dumps(self.config, indent=2)
with open(self.full_filename, "w") as f:
f.write(json_config)
Expand Down
26 changes: 19 additions & 7 deletions traitlets/traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,15 +43,12 @@

import contextlib
import enum
import inspect
import numbers
import os
import pathlib
import re
import sys
import types
import typing as t
from ast import literal_eval

from .utils.bunch import Bunch
from .utils.descriptions import add_article, class_of, describe, repr_type
Expand All @@ -63,6 +60,8 @@
SequenceTypes = (list, tuple, set, frozenset)

if t.TYPE_CHECKING:
import pathlib

from typing_extensions import TypeVar
else:
from typing import TypeVar
Expand Down Expand Up @@ -185,6 +184,8 @@ def _safe_literal_eval(s: str) -> t.Any:
Use only where types are ambiguous.
"""
from ast import literal_eval

try:
return literal_eval(s)
except (NameError, SyntaxError, ValueError):
Expand Down Expand Up @@ -559,7 +560,7 @@ def __init__(

if len(kwargs) > 0:
stacklevel = 1
f = inspect.currentframe()
f: types.FrameType | None = sys._getframe()
# count supers to determine stacklevel for warning
assert f is not None
while f.f_code.co_name == "__init__":
Expand Down Expand Up @@ -974,7 +975,7 @@ def __new__(
# ----------------------------------------------------------------
# Support of deprecated behavior allowing for TraitType types
# to be used instead of TraitType instances.
if inspect.isclass(v) and issubclass(v, TraitType):
if isinstance(v, type) and issubclass(v, TraitType):
warn(
"Traits should be given as instances, not types (for example, `Int()`, not `Int`)."
" Passing types is deprecated in traitlets 4.1.",
Expand Down Expand Up @@ -2114,7 +2115,7 @@ def __init__(
else:
klass = default_value

if not (inspect.isclass(klass) or isinstance(klass, str)):
if not isinstance(klass, (type, str)):
raise TraitError("A Type trait must specify a class.")

self.klass = klass
Expand Down Expand Up @@ -2278,7 +2279,7 @@ class or its subclasses. Our implementation is quite different
if klass is None:
klass = self.klass

if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, str)):
if (klass is not None) and isinstance(klass, (type, str)):
self.klass = klass
else:
raise TraitError(f"The klass attribute must be a class not: {klass!r}")
Expand Down Expand Up @@ -3510,6 +3511,8 @@ 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}")
from ast import literal_eval

try:
test = literal_eval(s)
except Exception:
Expand Down Expand Up @@ -3542,7 +3545,11 @@ def from_string_list(self, s_list: list[str]) -> T | None:
DeprecationWarning,
stacklevel=2,
)
from ast import literal_eval

return self.klass(literal_eval(r)) # type:ignore[call-arg]
import inspect

sig = inspect.signature(self.item_from_string)
if "index" in sig.parameters:
item_from_string = self.item_from_string
Expand Down Expand Up @@ -4069,6 +4076,7 @@ def from_string_list(self, s_list: list[str]) -> t.Any:
DeprecationWarning,
stacklevel=2,
)
from ast import literal_eval

return literal_eval(s_list[0])

Expand Down Expand Up @@ -4192,11 +4200,15 @@ class Path(TraitType["pathlib.Path", t.Union["pathlib.Path", str, "os.PathLike[s
info_text = "a filesystem path"

def validate(self, obj: t.Any, value: t.Any) -> pathlib.Path | None:
import pathlib

if isinstance(value, (str, os.PathLike)):
return pathlib.Path(value)
self.error(obj, value)

def from_string(self, s: str) -> pathlib.Path | None:
import pathlib

if self.allow_none and s == "None":
return None
return pathlib.Path(s)
Expand Down
3 changes: 2 additions & 1 deletion traitlets/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from __future__ import annotations

import os
import pathlib
from collections.abc import Sequence


Expand Down Expand Up @@ -51,6 +50,8 @@ def filefind(filename: str, path_dirs: Sequence[str] | None = None) -> str:
if os.path.isabs(filename) and os.path.isfile(filename):
return filename

import pathlib

if path_dirs is None:
path_dirs = ("",)
elif isinstance(path_dirs, str):
Expand Down
5 changes: 4 additions & 1 deletion traitlets/utils/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,15 @@
from __future__ import annotations

import copy
from inspect import Parameter, Signature, signature
from typing import Any, TypeVar

from ..traitlets import HasTraits, Undefined


def _get_default(value: Any) -> Any:
"""Get default argument value, given the trait default value."""
from inspect import Parameter

return Parameter.empty if value == Undefined else value


Expand All @@ -19,6 +20,8 @@ def _get_default(value: Any) -> Any:

def signature_has_traits(cls: type[T]) -> type[T]:
"""Return a decorated class with a constructor signature that contain Trait names as kwargs."""
from inspect import Parameter, Signature, signature

traits = [
(name, _get_default(value.default_value))
for name, value in cls.class_traits().items()
Expand Down
11 changes: 6 additions & 5 deletions traitlets/utils/descriptions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations

import inspect
import re
import types
from typing import Any
Expand Down Expand Up @@ -71,14 +70,14 @@ class name where an object was defined.
if isinstance(article, str):
article = article.lower()

if not inspect.isclass(value):
if not isinstance(value, type):
typename = type(value).__name__
else:
typename = value.__name__
if verbose:
typename = _prefix(value) + typename

if article == "the" or (article is None and not inspect.isclass(value)):
if article == "the" or (article is None and not isinstance(value, type)):
if name is not None:
result = f"{typename} {name}"
if article is not None:
Expand All @@ -87,7 +86,7 @@ class name where an object was defined.
return result
else:
tick_wrap = False
if inspect.isclass(value):
if isinstance(value, type):
name = value.__name__
elif isinstance(value, types.FunctionType):
name = value.__name__
Expand Down Expand Up @@ -123,6 +122,8 @@ def _prefix(value: Any) -> str:
if isinstance(value, types.MethodType):
name = describe(None, value.__self__, verbose=True) + "."
else:
import inspect

module = inspect.getmodule(value)
if module is not None and module.__name__ != "builtins":
name = module.__name__ + "."
Expand All @@ -136,7 +137,7 @@ def class_of(value: Any) -> Any:

For example 'an Image' or 'a PlotValue'.
"""
if inspect.isclass(value):
if isinstance(value, type):
return add_article(value.__name__)
else:
return class_of(type(value))
Expand Down
10 changes: 7 additions & 3 deletions traitlets/utils/getargspec.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@

from __future__ import annotations

import inspect
from functools import partial
from typing import Any
from typing import TYPE_CHECKING, Any

if TYPE_CHECKING:
import inspect

# Unmodified from sphinx below this line


def getargspec(func: Any) -> inspect.FullArgSpec:
"""Like inspect.getargspec but supports functools.partial as well."""
import inspect
from functools import partial

if inspect.ismethod(func):
func = func.__func__
if type(func) is partial:
Expand Down
3 changes: 2 additions & 1 deletion traitlets/utils/warnings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from __future__ import annotations

import inspect
import os
import typing as t
import warnings
Expand All @@ -20,6 +19,8 @@ def deprecated_method(method: t.Any, cls: t.Any, method_name: str, msg: str) ->
Uses warn_explicit to bind warning to method definition instead of triggering code,
which isn't relevant.
"""
import inspect

warn_msg = f"{cls.__name__}.{method_name} is deprecated in traitlets 4.1: {msg}"

for parent in inspect.getmro(cls):
Expand Down