From 2bea1313a97cbc4ea630e76ee6a6dc69d2519316 Mon Sep 17 00:00:00 2001 From: M Bussonnier Date: Mon, 13 Jul 2026 09:24:00 +0200 Subject: [PATCH 1/2] Fix broken examples and wrong docstrings in documentation Broken examples (all raised when run as written): - config.rst: the sample Configurable uses Integer but imports Int (NameError) - migration.rst: the observe_compat example read change['value'], a key that does not exist in change dicts (KeyError); use change['new'] - examples/myapp.py: the docstring described a 'shortname' tag feature that does not exist anywhere in traitlets (the tag was inert metadata); describe Application.aliases, the real mechanism, and drop the inert tag Wrong or incomplete docstrings: - api.rst claimed old_value is None when a dynamic-default trait is set before first read; it is actually the trait type's static default (or Undefined). Also fix a doubled word. - trait_types.rst still described Python 2 int/long behavior for Integer/Int/Long despite the package requiring Python 3 - import_item's Returns section claimed it returns a module; for dotted names it returns the named attribute, which can be any object - ArgParseConfigLoader.__init__ carried a copy-pasted Returns section describing a Config object; __init__ returns nothing - UseEnum's example dropped a stale Python 3.4/enum34 backport comment --- docs/source/api.rst | 5 ++++- docs/source/config.rst | 2 +- docs/source/migration.rst | 2 +- docs/source/trait_types.rst | 7 +++---- examples/myapp.py | 13 +++++++------ traitlets/config/loader.py | 5 ----- traitlets/traitlets.py | 1 - traitlets/utils/importstring.py | 6 ++++-- 8 files changed, 20 insertions(+), 21 deletions(-) diff --git a/docs/source/api.rst b/docs/source/api.rst index 268274f0..6c5a0bff 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -94,7 +94,10 @@ You can also add callbacks to a trait dynamically: If a trait attribute with a dynamic default value has another value set before it is used, the default will not be calculated. - Any callbacks on that trait will will fire, and *old_value* will be ``None``. + Any callbacks on that trait will fire, and *old_value* will be the + trait type's static default (e.g. ``''`` for :class:`Unicode`), or + ``traitlets.Undefined`` for trait types whose default can only be + computed dynamically (e.g. containers and :class:`Instance`). Validating proposed changes --------------------------- diff --git a/docs/source/config.rst b/docs/source/config.rst index 210c42ee..7472936c 100644 --- a/docs/source/config.rst +++ b/docs/source/config.rst @@ -125,7 +125,7 @@ subclass # Sample configurable: from traitlets.config.configurable import Configurable - from traitlets import Int, Float, Unicode, Bool + from traitlets import Integer, Float, Unicode, Bool class School(Configurable): diff --git a/docs/source/migration.rst b/docs/source/migration.rst index 7f755cf7..31a67b29 100644 --- a/docs/source/migration.rst +++ b/docs/source/migration.rst @@ -270,4 +270,4 @@ which automatically shims the deprecated signature into the new signature: @observe("path") @observe_compat # <- this allows super()._path_changed in subclasses to work with the old signature. def _path_changed(self, change): - self.prefix = os.path.dirname(change["value"]) + self.prefix = os.path.dirname(change["new"]) diff --git a/docs/source/trait_types.rst b/docs/source/trait_types.rst index c7fb785f..28ef4c97 100644 --- a/docs/source/trait_types.rst +++ b/docs/source/trait_types.rst @@ -16,14 +16,13 @@ Numbers .. autoclass:: Integer - An integer trait. On Python 2, this automatically uses the ``int`` or - ``long`` types as necessary. + An integer trait. .. class:: Int .. class:: Long - On Python 2, these are traitlets for values where the ``int`` and ``long`` - types are not interchangeable. On Python 3, they are both aliases for + These were traitlets for values where the Python 2 ``int`` and ``long`` + types were not interchangeable. Now they are both aliases for :class:`Integer`. In almost all situations, you should use :class:`Integer` instead of these. diff --git a/examples/myapp.py b/examples/myapp.py index e6c8984f..f559d3eb 100755 --- a/examples/myapp.py +++ b/examples/myapp.py @@ -20,14 +20,15 @@ to set the following options: * ``config``: set to ``True`` to make the attribute configurable. -* ``shortname``: by default, configurable attributes are set using the syntax - "Classname.attributename". At the command line, this is a bit verbose, so - we allow "shortnames" to be declared. Setting a shortname is optional, but - when you do this, you can set the option at the command line using the - syntax: "shortname=value". * ``help``: set the help string to display a help message when the ``-h`` option is given at the command line. The help string should be valid ReST. +By default, configurable attributes are set at the command line using the +syntax "--Classname.attributename=value". This is a bit verbose, so an +Application can declare ``aliases``, mapping a short name to a +"Classname.attributename" (see MyApp below); the option can then be set +with "--alias value". + When the config attribute of an Application is updated, it will fire all of the trait's events for all of the config=True attributes. """ @@ -51,7 +52,7 @@ class Foo(Configurable): i = Int(0, help="The integer i.").tag(config=True) j = Int(1, help="The integer j.").tag(config=True) - name = Unicode("Brian", help="First name.").tag(config=True, shortname="B") + name = Unicode("Brian", help="First name.").tag(config=True) mode = Enum(values=["on", "off", "other"], default_value="on").tag(config=True) def __init__(self, **kwargs): diff --git a/traitlets/config/loader.py b/traitlets/config/loader.py index f9eb5fe1..ece61d0e 100644 --- a/traitlets/config/loader.py +++ b/traitlets/config/loader.py @@ -832,11 +832,6 @@ def __init__( Dict of flags to full traitlets names for CLI parsing log Passed to `ConfigLoader` - - Returns - ------- - config : Config - The resulting Config object. """ classes = classes or [] super(CommandLineConfigLoader, self).__init__(log=log) diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index 9c33d2f3..00dde28d 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -4236,7 +4236,6 @@ class UseEnum(TraitType[t.Any, t.Any]): .. sourcecode:: python - # -- SINCE: Python 3.4 (or install backport: pip install enum34) import enum from traitlets import HasTraits, UseEnum diff --git a/traitlets/utils/importstring.py b/traitlets/utils/importstring.py index 203f79f0..63a8da2b 100644 --- a/traitlets/utils/importstring.py +++ b/traitlets/utils/importstring.py @@ -21,8 +21,10 @@ def import_item(name: str) -> Any: Returns ------- - mod : module object - The module that was imported. + mod : object + The imported object: for a dotted name ``foo.bar``, the ``bar`` + attribute of module ``foo`` (which may be any object); for an + un-dotted name, the module itself. """ if not isinstance(name, str): raise TypeError("import_item accepts strings, not '%s'." % type(name)) From 73d1d7d8a96537d332ccbea4ed437475e8dc73bb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 13 Jul 2026 10:32:55 +0200 Subject: [PATCH 2/2] Deprecate the Int, Long and CLong aliases Int and Long were separate traits back when Python 2 distinguished int and long; they are now just integers. Make Integer the canonical class and turn Int, Long (and the casting CLong) into deprecated subclasses that emit a DeprecationWarning on use, steering users to Integer / CInt. Co-Authored-By: Claude Opus 4.8 --- docs/source/trait_types.rst | 18 ++++-- tests/config/test_configurable.py | 8 +-- tests/test_traitlets.py | 26 ++++++--- traitlets/traitlets.py | 96 +++++++++++++++++++++++++++---- 4 files changed, 119 insertions(+), 29 deletions(-) diff --git a/docs/source/trait_types.rst b/docs/source/trait_types.rst index 28ef4c97..50c31524 100644 --- a/docs/source/trait_types.rst +++ b/docs/source/trait_types.rst @@ -21,24 +21,32 @@ Numbers .. class:: Int .. class:: Long - These were traitlets for values where the Python 2 ``int`` and ``long`` - types were not interchangeable. Now they are both aliases for - :class:`Integer`. + .. deprecated:: 5.16 + Use :class:`Integer` instead. - In almost all situations, you should use :class:`Integer` instead of these. + These were traitlets for values where the Python 2 ``int`` and ``long`` + types were not interchangeable. Now they are both deprecated aliases for + :class:`Integer` and emit a :exc:`DeprecationWarning` when used. .. autoclass:: Float .. autoclass:: Complex .. class:: CInt - CLong CFloat CComplex Casting variants of the above. When a value is assigned to the attribute, these will attempt to convert it by calling e.g. ``value = int(value)``. +.. class:: CLong + + .. deprecated:: 5.16 + Use :class:`CInt` instead. + + A deprecated alias for :class:`CInt`; emits a :exc:`DeprecationWarning` + when used. + Strings ------- diff --git a/tests/config/test_configurable.py b/tests/config/test_configurable.py index 68725888..b0607551 100644 --- a/tests/config/test_configurable.py +++ b/tests/config/test_configurable.py @@ -53,10 +53,6 @@ class MyConfigurable(Configurable): The integer b. Current: 4.0""" -# On Python 3, the Integer trait is a synonym for Int -mc_help = mc_help.replace("", "") -mc_help_inst = mc_help_inst.replace("", "") - class Foo(Configurable): a = Integer(0, help="The integer a.").tag(config=True) @@ -76,7 +72,7 @@ class Bar(Foo): foo_help = """Foo(Configurable) options ------------------------- ---Foo.a= +--Foo.a= The integer a. Default: 0 --Foo.b= @@ -88,7 +84,7 @@ class Bar(Foo): bar_help = """Bar(Foo) options ---------------- ---Bar.a= +--Bar.a= The integer a. Default: 0 --Bar.bdict =... diff --git a/tests/test_traitlets.py b/tests/test_traitlets.py index 60b790a9..226bbc6c 100644 --- a/tests/test_traitlets.py +++ b/tests/test_traitlets.py @@ -346,25 +346,37 @@ def test_trait_types_deprecated(self): with expected_warnings(["Traits should be given as instances"]): class C(HasTraits): - t = Int + t = Integer def test_trait_types_list_deprecated(self): with expected_warnings(["Traits should be given as instances"]): class C(HasTraits): - t = List(Int) + t = List(Integer) def test_trait_types_tuple_deprecated(self): with expected_warnings(["Traits should be given as instances"]): class C(HasTraits): - t = Tuple(Int) + t = Tuple(Integer) def test_trait_types_dict_deprecated(self): with expected_warnings(["Traits should be given as instances"]): class C(HasTraits): - t = Dict(Int) + t = Dict(Integer) + + def test_int_long_deprecated_aliases(self): + for cls, replacement in [(Int, "Integer"), (Long, "Integer"), (CLong, "CInt")]: + with expected_warnings([f"`{cls.__name__}` trait is deprecated"]): + trait = cls(3) + # deprecated aliases still behave like their replacement + assert isinstance(trait, Integer) + + class C(HasTraits): + x = trait + + assert C().x == 3 class TestHasDescriptorsMeta(TestCase): @@ -841,7 +853,7 @@ def test_trait_metadata_deprecated(self): with expected_warnings([r"metadata should be set using the \.tag\(\) method"]): class A(HasTraits): - i = Int(config_key="MY_VALUE") + i = Integer(config_key="MY_VALUE") a = A() self.assertEqual(a.trait_metadata("i", "config_key"), "MY_VALUE") @@ -890,9 +902,9 @@ def test_traits_metadata_deprecated(self): with expected_warnings([r"metadata should be set using the \.tag\(\) method"] * 2): class A(HasTraits): - i = Int(config_key="VALUE1", other_thing="VALUE2") + i = Integer(config_key="VALUE1", other_thing="VALUE2") f = Float(config_key="VALUE3", other_thing="VALUE2") - j = Int(0) + j = Integer(0) a = A() self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j)) diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index 00dde28d..d72b3b80 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -882,8 +882,8 @@ def tag(self, **metadata: t.Any) -> Self: Examples -------- - >>> Int(0).tag(config=True, sync=True) - + >>> Integer(0).tag(config=True, sync=True) + """ maybe_constructor_keywords = set(metadata.keys()).intersection( @@ -1058,7 +1058,7 @@ def setup_class(cls: MetaHasTraits, classdict: dict[str, t.Any]) -> None: # for instance ipywidgets. none_ok = trait.default_value is None and trait.allow_none if ( - type(trait) in [CInt, Int] + type(trait) in [CInt, Integer, Int, Long, CLong] and trait.min is None # type: ignore[attr-defined] and trait.max is None # type: ignore[attr-defined] and (isinstance(trait.default_value, int) or none_ok) @@ -2566,7 +2566,7 @@ def subclass_init(self, cls: type[t.Any]) -> None: def _validate_bounds( - trait: Int[t.Any, t.Any] | Float[t.Any, t.Any], obj: t.Any, value: t.Any + trait: Integer[t.Any, t.Any] | Float[t.Any, t.Any], obj: t.Any, value: t.Any ) -> t.Any: """ Validate that a number to be applied to a trait is between bounds. @@ -2592,15 +2592,15 @@ def _validate_bounds( # I = t.TypeVar('I', t.Optional[int], int) -class Int(TraitType[G, S]): - """An int trait.""" +class Integer(TraitType[G, S]): + """An integer trait.""" default_value = 0 info_text = "an int" @t.overload def __init__( - self: Int[int, int], + self: Integer[int, int], default_value: int | Sentinel = ..., allow_none: Literal[False] = ..., read_only: bool | None = ..., @@ -2612,7 +2612,7 @@ def __init__( @t.overload def __init__( - self: Int[int | None, int | None], + self: Integer[int | None, int | None], default_value: int | Sentinel | None = ..., allow_none: Literal[True] = ..., read_only: bool | None = ..., @@ -2665,7 +2665,7 @@ def subclass_init(self, cls: type[t.Any]) -> None: pass # fully opt out of instance_init -class CInt(Int[G, S]): +class CInt(Integer[G, S]): """A casting version of the int trait.""" if t.TYPE_CHECKING: @@ -2713,8 +2713,82 @@ def validate(self, obj: t.Any, value: t.Any) -> G: return _validate_bounds(self, obj, value) # type:ignore[no-any-return] -Long, CLong = Int, CInt -Integer = Int +class Int(Integer[G, S]): + """A deprecated alias for :class:`Integer`. + + .. deprecated:: 5.16 + Use :class:`Integer` instead. ``Int`` and ``Long`` used to be distinct + traits back when Python 2 had separate ``int`` and ``long`` types; they + are now both just integers. + """ + + if t.TYPE_CHECKING: + + @t.overload + def __init__( + self: Int[int, int], + default_value: int | Sentinel = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ) -> None: + ... + + @t.overload + def __init__( + self: Int[int | None, int | None], + default_value: int | Sentinel | None = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ) -> None: + ... + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warn( + "The `Int` trait is deprecated since traitlets 5.16, use `Integer` instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type:ignore[misc] + + +class Long(Integer[G, S]): + """A deprecated alias for :class:`Integer`. + + .. deprecated:: 5.16 + Use :class:`Integer` instead. ``Int`` and ``Long`` used to be distinct + traits back when Python 2 had separate ``int`` and ``long`` types; they + are now both just integers. + """ + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warn( + "The `Long` trait is deprecated since traitlets 5.16, use `Integer` instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type:ignore[misc] + + +class CLong(CInt[G, S]): + """A deprecated alias for :class:`CInt`. + + .. deprecated:: 5.16 + Use :class:`CInt` instead. + """ + + def __init__(self, *args: t.Any, **kwargs: t.Any) -> None: + warn( + "The `CLong` trait is deprecated since traitlets 5.16, use `CInt` instead.", + DeprecationWarning, + stacklevel=2, + ) + super().__init__(*args, **kwargs) # type:ignore[misc] class Float(TraitType[G, S]):