Skip to content
Draft
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
44 changes: 44 additions & 0 deletions tests/config/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,6 +466,33 @@ def test_flatten_aliases(self):
# this would be app.config.Application.log_level if it failed:
self.assertEqual(app.config.MyApp.log_level, "CRITICAL")

def test_flatten_aliases_tuple_keys(self):
# tuple alias keys must be exploded into one entry per name, so
# that the loader can detect collisions between flags and aliases
app = MyApp()
flags, aliases = app.flatten_flags()
self.assertEqual(aliases["fooi"], "Foo.i")
self.assertEqual(aliases["i"], "Foo.i")
self.assertNotIn(("fooi", "i"), aliases)

def test_flag_tuple_alias_collision(self):
# a flag sharing a name with one member of a tuple alias used to
# crash argparse with 'conflicting option strings'
class CollisionApp(Application):
classes = List([Bar]) # type:ignore[assignment]
aliases = {("b", "bee"): "Bar.b"}
flags = {"b": ({"Bar": {"enabled": False}}, "Disable Bar")}

# with an argument it acts as the alias
app = CollisionApp()
app.parse_command_line(["-b", "5"])
self.assertEqual(app.config.Bar.b, 5)

# without an argument it acts as the flag
app = CollisionApp()
app.parse_command_line(["-b"])
self.assertEqual(app.config.Bar.enabled, False)

def test_extra_args(self):
app = MyApp()
app.parse_command_line(["--Bar.b=5", "extra", "args", "--disable"])
Expand Down Expand Up @@ -899,6 +926,23 @@ def test_logging_teardown_on_error(capsys, caplogconfig):
assert len(caplogconfig) == 1 # logging was configured


def test_get_logger_after_application():
# get_logger must pick up the Application's logger even if it was
# first called (returning the fallback) before the Application existed,
# and fall back again once the Application is torn down
import traitlets.log

Application.clear_instance()
try:
fallback = traitlets.log.get_logger()
assert fallback.name == "traitlets"
app = Application.instance()
assert traitlets.log.get_logger() is app.log
finally:
Application.clear_instance()
assert traitlets.log.get_logger() is fallback


if __name__ == "__main__":
# for test_help_output:
MyApp.launch_instance()
38 changes: 38 additions & 0 deletions tests/test_traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,44 @@ class A(HasTraits):
self.assertTrue(a.has_trait("f"))
self.assertFalse(a.has_trait("g"))

def test_trait_events(self):
class A(HasTraits):
i = Int()

@observe("i")
def _on_i(self, change):
pass

class B(A):
f = Float()

@observe("f")
def _on_f(self, change):
pass

self.assertEqual(sorted(B.trait_events()), ["_on_f", "_on_i"])
self.assertEqual(list(B.trait_events("f")), ["_on_f"])
self.assertEqual(list(B.trait_events("i")), ["_on_i"])

def test_class_own_trait_events(self):
class A(HasTraits):
i = Int()

@observe("i")
def _on_i(self, change):
pass

class B(A):
f = Float()

@observe("f")
def _on_f(self, change):
pass

self.assertEqual(list(A.class_own_trait_events("i")), ["_on_i"])
self.assertEqual(list(B.class_own_trait_events("f")), ["_on_f"])
self.assertEqual(list(B.class_own_trait_events("i")), [])

def test_trait_has_value(self):
class A(HasTraits):
i = Int()
Expand Down
4 changes: 2 additions & 2 deletions traitlets/config/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -753,8 +753,8 @@ def flatten_flags(self) -> tuple[dict[str, t.Any], dict[str, t.Any]]:
if len(children) == 1:
# exactly one descendent, promote alias
cls = children[0] # type:ignore[assignment]
if not isinstance(aliases, tuple): # type:ignore[unreachable]
alias = (alias,) # type:ignore[assignment]
if not isinstance(alias, tuple):
alias = (alias,)
for al in alias:
aliases[al] = ".".join([cls, trait]) # type:ignore[list-item]

Expand Down
23 changes: 9 additions & 14 deletions traitlets/log.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,20 @@
import logging
from typing import Any

_logger: logging.Logger | logging.LoggerAdapter[Any] | None = None
# Add a NullHandler to silence warnings about not being
# initialized, per best practice for libraries.
_fallback = logging.getLogger("traitlets")
_fallback.addHandler(logging.NullHandler())


def get_logger() -> logging.Logger | logging.LoggerAdapter[Any]:
"""Grab the global logger instance.

If a global Application is instantiated, grab its logger.
Otherwise, grab the root logger.
Otherwise, grab the 'traitlets' library logger.
"""
global _logger # noqa: PLW0603
from .config import Application

if _logger is None:
from .config import Application

if Application.initialized():
_logger = Application.instance().log
else:
_logger = logging.getLogger("traitlets")
# Add a NullHandler to silence warnings about not being
# initialized, per best practice for libraries.
_logger.addHandler(logging.NullHandler())
return _logger
if Application.initialized():
return Application.instance().log
return _fallback
59 changes: 0 additions & 59 deletions traitlets/tests/test_traitlets.py

This file was deleted.

11 changes: 2 additions & 9 deletions traitlets/traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -1967,14 +1967,10 @@ def trait_metadata(self, traitname: str, key: str, default: t.Any = None) -> t.A
def class_own_trait_events(cls: type[HasTraits], name: str) -> dict[str, EventHandler]:
"""Get a dict of all event handlers defined on this class, not a parent.

Works like ``event_handlers``, except for excluding traits from parents.
Works like ``trait_events``, except for excluding traits from parents.
"""
sup = super(cls, cls)
return {
n: e
for (n, e) in cls.events(name).items() # type:ignore[attr-defined]
if getattr(sup, n, None) is not e
}
return {n: e for (n, e) in cls.trait_events(name).items() if getattr(sup, n, None) is not e}

@classmethod
def trait_events(cls: type[HasTraits], name: str | None = None) -> dict[str, EventHandler]:
Expand All @@ -1997,9 +1993,6 @@ def trait_events(cls: type[HasTraits], name: str | None = None) -> dict[str, Eve
events[k] = v
elif name in v.trait_names: # type:ignore[attr-defined]
events[k] = v
elif hasattr(v, "tags"):
if cls.trait_names(**v.tags):
events[k] = v
return events


Expand Down
Loading