From 245f33eacee8a9991addfa314a4fc8fea8af2023 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 3ebb10e7..930af29c 100644 --- a/traitlets/config/loader.py +++ b/traitlets/config/loader.py @@ -823,11 +823,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 18ac9430..81830abf 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -4235,7 +4235,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 37c8d58291c1a0c282c2896d95b3608b93206417 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 16 Jul 2026 14:17:13 +0200 Subject: [PATCH 2/2] Deprecate the Long and CLong aliases `Long` and `CLong` were plain aliases for `Int` and `CInt`, dating back to Python 2 where `int` and `long` were distinct types. They are now separate subclasses that emit a DeprecationWarning on instantiation. `Int` itself is not deprecated; `Integer` remains an alias for it. --- docs/source/config.rst | 2 +- docs/source/trait_types.rst | 26 ++++++++++++++----- tests/config/test_application.py | 14 ++++++++-- tests/config/test_configurable.py | 8 ++---- tests/test_traitlets.py | 12 +++++++++ traitlets/traitlets.py | 43 +++++++++++++++++++++++++++---- 6 files changed, 84 insertions(+), 21 deletions(-) diff --git a/docs/source/config.rst b/docs/source/config.rst index 7472936c..210c42ee 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 Integer, Float, Unicode, Bool + from traitlets import Int, Float, Unicode, Bool class School(Configurable): diff --git a/docs/source/trait_types.rst b/docs/source/trait_types.rst index 28ef4c97..9a892fd0 100644 --- a/docs/source/trait_types.rst +++ b/docs/source/trait_types.rst @@ -14,31 +14,43 @@ Trait Types Numbers ------- -.. autoclass:: Integer +.. autoclass:: Int An integer trait. -.. class:: Int + +.. class:: Integer + + An alias for :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. + This (with Int) were values where the Python 2 ``int`` and ``long`` + types were not interchangeable. Now Long is a deprecated aliases for + :class:`Int` 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_application.py b/tests/config/test_application.py index bfe30229..56700bd5 100644 --- a/tests/config/test_application.py +++ b/tests/config/test_application.py @@ -19,7 +19,18 @@ import pytest -from traitlets import Bool, Bytes, Dict, HasTraits, Integer, List, Set, Tuple, Unicode +from traitlets import ( + Bool, + Bytes, + Dict, + HasTraits, + Int, + Integer, + List, + Set, + Tuple, + Unicode, +) from traitlets.config.application import Application from traitlets.config.configurable import Configurable from traitlets.config.loader import Config, KVArgParseConfigLoader @@ -778,7 +789,6 @@ def test_show_config_json(capsys): def test_deep_alias(): - from traitlets import Int from traitlets.config import Application, Configurable class Foo(Configurable): diff --git a/tests/config/test_configurable.py b/tests/config/test_configurable.py index 68725888..ab6f2180 100644 --- a/tests/config/test_configurable.py +++ b/tests/config/test_configurable.py @@ -37,7 +37,7 @@ class MyConfigurable(Configurable): mc_help = """MyConfigurable(Configurable) options ------------------------------------ ---MyConfigurable.a= +--MyConfigurable.a= The integer a. Default: 1 --MyConfigurable.b= @@ -46,17 +46,13 @@ class MyConfigurable(Configurable): mc_help_inst = """MyConfigurable(Configurable) options ------------------------------------ ---MyConfigurable.a= +--MyConfigurable.a= The integer a. Current: 5 --MyConfigurable.b= 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) diff --git a/tests/test_traitlets.py b/tests/test_traitlets.py index 933843a6..5e4099ae 100644 --- a/tests/test_traitlets.py +++ b/tests/test_traitlets.py @@ -366,6 +366,18 @@ def test_trait_types_dict_deprecated(self): class C(HasTraits): t = Dict(Int) + def test_long_deprecated_aliases(self): + for cls, replacement in [(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): def test_metaclass(self): diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index 81830abf..99faa03e 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -1057,7 +1057,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, 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) @@ -2592,7 +2592,7 @@ def _validate_bounds( class Int(TraitType[G, S]): - """An int trait.""" + """An integer trait.""" default_value = 0 info_text = "an int" @@ -2712,10 +2712,43 @@ 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 Long(Int[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]): """A float trait.""" @@ -3910,11 +3943,11 @@ def __init__( d2['n'] must be an integer d2['s'] must be text - >>> d2 = Dict(per_key_traits={"n": Integer(), "s": Unicode()}) + >>> d2 = Dict(per_key_traits={"n": Int(), "s": Unicode()}) d3's keys must be text d3's values must be integers - >>> d3 = Dict(value_trait=Integer(), key_trait=Unicode()) + >>> d3 = Dict(value_trait=Int(), key_trait=Unicode()) """