diff options
author | robot-contrib <robot-contrib@yandex-team.com> | 2023-09-30 10:56:33 +0300 |
---|---|---|
committer | robot-contrib <robot-contrib@yandex-team.com> | 2023-09-30 11:11:27 +0300 |
commit | 4fc6a19dd6b98b1cba250bcd94406177fddd236e (patch) | |
tree | f496e5b3eaa64447ad6b3de6db6c404b1f65648d | |
parent | 5a6373c9d09bbfb7094f9992a4531477bb97829e (diff) | |
download | ydb-4fc6a19dd6b98b1cba250bcd94406177fddd236e.tar.gz |
Update contrib/python/traitlets/py3 to 5.10.0
19 files changed, 966 insertions, 475 deletions
diff --git a/contrib/python/traitlets/py3/.dist-info/METADATA b/contrib/python/traitlets/py3/.dist-info/METADATA index 1c9d322cc3..96b7735ddf 100644 --- a/contrib/python/traitlets/py3/.dist-info/METADATA +++ b/contrib/python/traitlets/py3/.dist-info/METADATA @@ -1,16 +1,10 @@ Metadata-Version: 2.1 Name: traitlets -Version: 5.9.0 +Version: 5.10.0 Summary: Traitlets Python configuration system Project-URL: Homepage, https://github.com/ipython/traitlets Author-email: IPython Development Team <ipython-dev@python.org> -License: # Licensing terms - - Traitlets is adapted from enthought.traits, Copyright (c) Enthought, Inc., - under the terms of the Modified BSD License. - - This project is licensed under the terms of the Modified BSD License - (also known as New or Revised or 3-Clause BSD), as follows: +License: BSD 3-Clause License - Copyright (c) 2001-, IPython Development Team @@ -19,56 +13,28 @@ License: # Licensing terms Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or - other materials provided with the distribution. + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. - Neither the name of the IPython Development Team nor the names of its - contributors may be used to endorse or promote products derived from this - software without specific prior written permission. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ## About the IPython Development Team - - The IPython Development Team is the set of all contributors to the IPython project. - This includes all of the IPython subprojects. - - The core team that coordinates development on GitHub can be found here: - https://github.com/jupyter/. - - ## Our Copyright Policy - - IPython uses a shared copyright model. Each contributor maintains copyright - over their contributions to IPython. But, it is important to note that these - contributions are typically only changes to the repositories. Thus, the IPython - source code, in its entirety is not the copyright of any single person or - institution. Instead, it is the collective copyright of the entire IPython - Development Team. If individual contributors want to maintain a record of what - changes/contributions they have specific copyright on, they should indicate - their copyright in the commit message of the change, when they commit the - change to one of the IPython repositories. - - With this in mind, the following banner should be used in any source code file - to indicate the copyright and license terms: - - ``` - # Copyright (c) IPython Development Team. - # Distributed under the terms of the Modified BSD License. - ``` -License-File: COPYING.md +License-File: LICENSE Keywords: Interactive,Interpreter,Shell,Web Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Science/Research @@ -76,23 +42,24 @@ Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: BSD License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 -Requires-Python: >=3.7 +Requires-Python: >=3.8 Provides-Extra: docs Requires-Dist: myst-parser; extra == 'docs' Requires-Dist: pydata-sphinx-theme; extra == 'docs' Requires-Dist: sphinx; extra == 'docs' Provides-Extra: test -Requires-Dist: argcomplete>=2.0; extra == 'test' +Requires-Dist: argcomplete>=3.0.3; extra == 'test' +Requires-Dist: mypy>=1.5.1; extra == 'test' Requires-Dist: pre-commit; extra == 'test' -Requires-Dist: pytest; extra == 'test' Requires-Dist: pytest-mock; extra == 'test' +Requires-Dist: pytest-mypy-testing; extra == 'test' +Requires-Dist: pytest<7.5,>=7.0; extra == 'test' Description-Content-Type: text/markdown # Traitlets [![Tests](https://github.com/ipython/traitlets/actions/workflows/tests.yml/badge.svg)](https://github.com/ipython/traitlets/actions/workflows/tests.yml) [![Documentation Status](https://readthedocs.org/projects/traitlets/badge/?version=latest)](https://traitlets.readthedocs.io/en/latest/?badge=latest) -[![codecov](https://codecov.io/gh/ipython/traitlets/branch/main/graph/badge.svg?token=HcsbLGEmI1)](https://codecov.io/gh/ipython/traitlets) [![Tidelift](https://tidelift.com/subscription/pkg/pypi-traitlets)](https://tidelift.com/badges/package/pypi/traitlets) | | | @@ -278,3 +245,31 @@ with parity_check.hold_trait_notifications(): However, we **recommend** that custom cross-validators don't modify the state of the HasTraits instance. + +## About the IPython Development Team + +The IPython Development Team is the set of all contributors to the IPython project. +This includes all of the IPython subprojects. + +The core team that coordinates development on GitHub can be found here: +https://github.com/jupyter/. + +## Our Copyright Policy + +IPython uses a shared copyright model. Each contributor maintains copyright +over their contributions to IPython. But, it is important to note that these +contributions are typically only changes to the repositories. Thus, the IPython +source code, in its entirety is not the copyright of any single person or +institution. Instead, it is the collective copyright of the entire IPython +Development Team. If individual contributors want to maintain a record of what +changes/contributions they have specific copyright on, they should indicate +their copyright in the commit message of the change, when they commit the +change to one of the IPython repositories. + +With this in mind, the following banner should be used in any source code file +to indicate the copyright and license terms: + +``` +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +``` diff --git a/contrib/python/traitlets/py3/COPYING.md b/contrib/python/traitlets/py3/COPYING.md deleted file mode 100644 index b4325343eb..0000000000 --- a/contrib/python/traitlets/py3/COPYING.md +++ /dev/null @@ -1,64 +0,0 @@ -# Licensing terms - -Traitlets is adapted from enthought.traits, Copyright (c) Enthought, Inc., -under the terms of the Modified BSD License. - -This project is licensed under the terms of the Modified BSD License -(also known as New or Revised or 3-Clause BSD), as follows: - -- Copyright (c) 2001-, IPython Development Team - -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -Neither the name of the IPython Development Team nor the names of its -contributors may be used to endorse or promote products derived from this -software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -## About the IPython Development Team - -The IPython Development Team is the set of all contributors to the IPython project. -This includes all of the IPython subprojects. - -The core team that coordinates development on GitHub can be found here: -https://github.com/jupyter/. - -## Our Copyright Policy - -IPython uses a shared copyright model. Each contributor maintains copyright -over their contributions to IPython. But, it is important to note that these -contributions are typically only changes to the repositories. Thus, the IPython -source code, in its entirety is not the copyright of any single person or -institution. Instead, it is the collective copyright of the entire IPython -Development Team. If individual contributors want to maintain a record of what -changes/contributions they have specific copyright on, they should indicate -their copyright in the commit message of the change, when they commit the -change to one of the IPython repositories. - -With this in mind, the following banner should be used in any source code file -to indicate the copyright and license terms: - -``` -# Copyright (c) IPython Development Team. -# Distributed under the terms of the Modified BSD License. -``` diff --git a/contrib/python/traitlets/py3/LICENSE b/contrib/python/traitlets/py3/LICENSE new file mode 100644 index 0000000000..76910b096a --- /dev/null +++ b/contrib/python/traitlets/py3/LICENSE @@ -0,0 +1,30 @@ +BSD 3-Clause License + +- Copyright (c) 2001-, IPython Development Team + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/contrib/python/traitlets/py3/README.md b/contrib/python/traitlets/py3/README.md index e7b32e2b2a..259c3d0798 100644 --- a/contrib/python/traitlets/py3/README.md +++ b/contrib/python/traitlets/py3/README.md @@ -2,7 +2,6 @@ [![Tests](https://github.com/ipython/traitlets/actions/workflows/tests.yml/badge.svg)](https://github.com/ipython/traitlets/actions/workflows/tests.yml) [![Documentation Status](https://readthedocs.org/projects/traitlets/badge/?version=latest)](https://traitlets.readthedocs.io/en/latest/?badge=latest) -[![codecov](https://codecov.io/gh/ipython/traitlets/branch/main/graph/badge.svg?token=HcsbLGEmI1)](https://codecov.io/gh/ipython/traitlets) [![Tidelift](https://tidelift.com/subscription/pkg/pypi-traitlets)](https://tidelift.com/badges/package/pypi/traitlets) | | | @@ -188,3 +187,31 @@ with parity_check.hold_trait_notifications(): However, we **recommend** that custom cross-validators don't modify the state of the HasTraits instance. + +## About the IPython Development Team + +The IPython Development Team is the set of all contributors to the IPython project. +This includes all of the IPython subprojects. + +The core team that coordinates development on GitHub can be found here: +https://github.com/jupyter/. + +## Our Copyright Policy + +IPython uses a shared copyright model. Each contributor maintains copyright +over their contributions to IPython. But, it is important to note that these +contributions are typically only changes to the repositories. Thus, the IPython +source code, in its entirety is not the copyright of any single person or +institution. Instead, it is the collective copyright of the entire IPython +Development Team. If individual contributors want to maintain a record of what +changes/contributions they have specific copyright on, they should indicate +their copyright in the commit message of the change, when they commit the +change to one of the IPython repositories. + +With this in mind, the following banner should be used in any source code file +to indicate the copyright and license terms: + +``` +# Copyright (c) IPython Development Team. +# Distributed under the terms of the Modified BSD License. +``` diff --git a/contrib/python/traitlets/py3/traitlets/__init__.py b/contrib/python/traitlets/py3/traitlets/__init__.py index be890981f1..96ebe57f1b 100644 --- a/contrib/python/traitlets/py3/traitlets/__init__.py +++ b/contrib/python/traitlets/py3/traitlets/__init__.py @@ -1,12 +1,11 @@ """Traitlets Python configuration system""" -from warnings import warn - from . import traitlets from ._version import __version__, version_info from .traitlets import * from .utils.bunch import Bunch from .utils.decorators import signature_has_traits from .utils.importstring import import_item +from .utils.warnings import warn __all__ = [ "traitlets", diff --git a/contrib/python/traitlets/py3/traitlets/_version.py b/contrib/python/traitlets/py3/traitlets/_version.py index 6e09af1b91..c7cae1fe0b 100644 --- a/contrib/python/traitlets/py3/traitlets/_version.py +++ b/contrib/python/traitlets/py3/traitlets/_version.py @@ -5,7 +5,7 @@ import re from typing import List # Version string must appear intact for hatch versioning -__version__ = "5.9.0" +__version__ = "5.10.0" # Build up version_info tuple for backwards compatibility pattern = r"(?P<major>\d+).(?P<minor>\d+).(?P<patch>\d+)(?P<rest>.*)" diff --git a/contrib/python/traitlets/py3/traitlets/config/application.py b/contrib/python/traitlets/py3/traitlets/config/application.py index 3cffa6b008..9786e22416 100644 --- a/contrib/python/traitlets/py3/traitlets/config/application.py +++ b/contrib/python/traitlets/py3/traitlets/config/application.py @@ -149,21 +149,29 @@ class Application(SingletonConfigurable): # The name of the application, will usually match the name of the command # line application - name: t.Union[str, Unicode] = Unicode("application") + name: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode("application") # The description of the application that is printed at the beginning # of the help. - description: t.Union[str, Unicode] = Unicode("This is an application.") + description: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode( + "This is an application." + ) # default section descriptions - option_description: t.Union[str, Unicode] = Unicode(option_description) - keyvalue_description: t.Union[str, Unicode] = Unicode(keyvalue_description) - subcommand_description: t.Union[str, Unicode] = Unicode(subcommand_description) + option_description: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode( + option_description + ) + keyvalue_description: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode( + keyvalue_description + ) + subcommand_description: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode( + subcommand_description + ) python_config_loader_class = PyFileConfigLoader json_config_loader_class = JSONFileConfigLoader # The usage and example string that goes at the end of the help string. - examples: t.Union[str, Unicode] = Unicode() + examples: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode() # A sequence of Configurable subclasses whose config=True attributes will # be exposed at the command line. @@ -190,18 +198,18 @@ class Application(SingletonConfigurable): yield parent # The version string of this application. - version: t.Union[str, Unicode] = Unicode("0.0") + version: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode("0.0") # the argv used to initialize the application argv: t.Union[t.List[str], List] = List() # Whether failing to load config files should prevent startup - raise_config_file_errors: t.Union[bool, Bool] = Bool( + raise_config_file_errors: t.Union[bool, Bool[bool, t.Union[bool, int]]] = Bool( TRAITLETS_APPLICATION_RAISE_CONFIG_FILE_ERROR ) # The log level for the application - log_level: t.Union[str, int, Enum] = Enum( + log_level: t.Union[str, int, Enum[t.Any, t.Any]] = Enum( (0, 10, 20, 30, 40, 50, "DEBUG", "INFO", "WARN", "ERROR", "CRITICAL"), default_value=logging.WARN, help="Set the log level by value or name.", @@ -209,11 +217,11 @@ class Application(SingletonConfigurable): _log_formatter_cls = LevelFormatter - log_datefmt: t.Union[str, Unicode] = Unicode( + log_datefmt: t.Union[str, Unicode[str, t.Union[str, bytes]]] = Unicode( "%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[str, t.Union[str, bytes]]] = Unicode( "[%(name)s]%(highlevel)s %(message)s", help="The Logging format template", ).tag(config=True) @@ -420,11 +428,11 @@ class Application(SingletonConfigurable): _loaded_config_files = List() - show_config: t.Union[bool, Bool] = Bool( + show_config: t.Union[bool, Bool[bool, t.Union[bool, int]]] = Bool( 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, t.Union[bool, int]]] = Bool( help="Instead of starting the Application, dump configuration to stdout (as JSON)" ).tag(config=True) @@ -436,7 +444,7 @@ class Application(SingletonConfigurable): def _show_config_changed(self, change): if change.new: self._save_start = self.start - self.start = self.start_show_config # type:ignore[assignment] + self.start = self.start_show_config # type:ignore[method-assign] def __init__(self, **kwargs): SingletonConfigurable.__init__(self, **kwargs) @@ -446,7 +454,7 @@ class Application(SingletonConfigurable): if cls not in self.classes: if self.classes is cls.classes: # class attr, assign instead of insert - self.classes = [cls] + self.classes + self.classes = [cls, *self.classes] else: self.classes.insert(0, self.__class__) @@ -503,12 +511,7 @@ class Application(SingletonConfigurable): for traitname in sorted(class_config): value = class_config[traitname] - print( - " .{} = {}".format( - traitname, - pprint.pformat(value, **pformat_kwargs), - ) - ) + print(f" .{traitname} = {pprint.pformat(value, **pformat_kwargs)}") def print_alias_help(self): """Print the alias parts of the help.""" @@ -947,7 +950,7 @@ class Application(SingletonConfigurable): """Load config files by filename and path.""" filename, ext = os.path.splitext(filename) new_config = Config() - for (config, fname) in self._load_config_files( + for config, fname in self._load_config_files( filename, path=path, log=self.log, diff --git a/contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py b/contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py index afda7f86d2..ee1e51b492 100644 --- a/contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py +++ b/contrib/python/traitlets/py3/traitlets/config/argcomplete_config.py @@ -9,8 +9,8 @@ import os import typing as t try: - import argcomplete # type: ignore[import] - from argcomplete import CompletionFinder + import argcomplete + from argcomplete import CompletionFinder # type:ignore except ImportError: # This module and its utility methods are written to not crash even # if argcomplete is not installed. @@ -20,8 +20,8 @@ except ImportError: raise ModuleNotFoundError("No module named 'argcomplete'") raise AttributeError(f"argcomplete stub module has no attribute '{attr}'") - argcomplete = StubModule() - CompletionFinder = object + argcomplete = StubModule() # type:ignore + CompletionFinder = object # type:ignore def get_argcomplete_cwords() -> t.Optional[t.List[str]]: @@ -45,7 +45,9 @@ def get_argcomplete_cwords() -> t.Optional[t.List[str]]: cword_suffix, comp_words, last_wordbreak_pos, - ) = argcomplete.split_line(comp_line, comp_point) + ) = argcomplete.split_line( # type:ignore + comp_line, comp_point + ) except ModuleNotFoundError: return None @@ -73,7 +75,9 @@ def increment_argcomplete_index(): os.environ["_ARGCOMPLETE"] = str(int(os.environ["_ARGCOMPLETE"]) + 1) except Exception: try: - argcomplete.debug("Unable to increment $_ARGCOMPLETE", os.environ["_ARGCOMPLETE"]) + argcomplete.debug( # type:ignore + "Unable to increment $_ARGCOMPLETE", os.environ["_ARGCOMPLETE"] + ) except (KeyError, ModuleNotFoundError): pass @@ -196,7 +200,7 @@ class ExtendedCompletionFinder(CompletionFinder): # Instead, check if comp_words only consists of the script, # if so check if any subcommands start with cword_prefix. if self.subcommands and len(comp_words) == 1: - argcomplete.debug("Adding subcommands for", cword_prefix) + argcomplete.debug("Adding subcommands for", cword_prefix) # type:ignore completions.extend(subc for subc in self.subcommands if subc.startswith(cword_prefix)) return completions diff --git a/contrib/python/traitlets/py3/traitlets/config/configurable.py b/contrib/python/traitlets/py3/traitlets/config/configurable.py index effa8e429d..1bfa045736 100644 --- a/contrib/python/traitlets/py3/traitlets/config/configurable.py +++ b/contrib/python/traitlets/py3/traitlets/config/configurable.py @@ -5,7 +5,6 @@ import logging -import warnings from copy import deepcopy from textwrap import dedent @@ -20,6 +19,7 @@ from traitlets.traitlets import ( observe_compat, validate, ) +from traitlets.utils import warnings from traitlets.utils.text import indent, wrap_paragraphs from .loader import Config, DeferredConfig, LazyConfigValue, _is_section_key @@ -43,7 +43,6 @@ class MultipleInstanceError(ConfigurableError): class Configurable(HasTraits): - config = Instance(Config, (), {}) parent = Instance("traitlets.config.configurable.Configurable", allow_none=True) @@ -183,13 +182,15 @@ class Configurable(HasTraits): 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] + + def warn(msg): + return warnings.warn(msg, UserWarning, stacklevel=9) + matches = get_close_matches(name, traits) - msg = "Config option `{option}` not recognized by `{klass}`.".format( - option=name, klass=self.__class__.__name__ - ) + msg = f"Config option `{name}` not recognized by `{self.__class__.__name__}`." if len(matches) == 1: msg += f" Did you mean `{matches[0]}`?" @@ -453,13 +454,16 @@ class LoggingConfigurable(Configurable): # warn about unsupported type, but be lenient to allow for duck typing warnings.warn( f"{self.__class__.__name__}.log should be a Logger or LoggerAdapter," - f" got {proposal.value}." + f" got {proposal.value}.", + UserWarning, + stacklevel=2, ) return proposal.value @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 @@ -558,8 +562,8 @@ class SingletonConfigurable(LoggingConfigurable): return cls._instance else: raise MultipleInstanceError( - "An incompatible sibling of '%s' is already instantiated" - " as singleton: %s" % (cls.__name__, type(cls._instance).__name__) + f"An incompatible sibling of '{cls.__name__}' is already instantiated" + f" as singleton: {type(cls._instance).__name__}" ) @classmethod diff --git a/contrib/python/traitlets/py3/traitlets/config/loader.py b/contrib/python/traitlets/py3/traitlets/config/loader.py index c1834a9bc4..1c6b8e8c1a 100644 --- a/contrib/python/traitlets/py3/traitlets/config/loader.py +++ b/contrib/python/traitlets/py3/traitlets/config/loader.py @@ -11,11 +11,10 @@ import os import re import sys import typing as t -import warnings from traitlets.traitlets import Any, Container, Dict, HasTraits, List, Undefined -from ..utils import cast_unicode, filefind +from ..utils import cast_unicode, filefind, warnings # ----------------------------------------------------------------------------- # Exceptions @@ -97,9 +96,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""" @@ -342,14 +341,14 @@ class Config(dict): # type:ignore[type-arg] dict.__setitem__(self, key, v) return v else: - raise KeyError + raise def __setitem__(self, key, value): if _is_section_key(key): if not isinstance(value, Config): raise ValueError( "values whose keys begin with an uppercase " - "char must be Config instances: %r, %r" % (key, value) + f"char must be Config instances: {key!r}, {value!r}" ) dict.__setitem__(self, key, value) @@ -1037,8 +1036,8 @@ class KVArgParseConfigLoader(ArgParseConfigLoader): # flag sets 'action', so can't have flag & alias with custom action # on the same name raise ArgumentError( - "The alias `%s` for the 'append' sequence " - "config-trait `%s` cannot be also a flag!'" % (key, traitname) + f"The alias `{key}` for the 'append' sequence " + f"config-trait `{traitname}` cannot be also a flag!'" ) # For argcomplete, check if any either an argcompleter metadata tag or method # is available. If so, it should be a callable which takes the command-line key @@ -1114,7 +1113,7 @@ class KVArgParseConfigLoader(ArgParseConfigLoader): ) -> None: """If argcomplete is enabled, allow triggering command-line autocompletion""" try: - import argcomplete # type: ignore[import] # noqa + import argcomplete # noqa except ImportError: return diff --git a/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py b/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py index 92c2d64d67..a69d89f9e4 100644 --- a/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py +++ b/contrib/python/traitlets/py3/traitlets/config/sphinxdoc.py @@ -124,10 +124,10 @@ def reverse_aliases(app): # Treat flags which set one trait to True as aliases. for flag, (cfg, _) in app.flags.items(): if len(cfg) == 1: - classname = list(cfg)[0] + classname = next(iter(cfg)) cls_cfg = cfg[classname] if len(cls_cfg) == 1: - traitname = list(cls_cfg)[0] + traitname = next(iter(cls_cfg)) if cls_cfg[traitname] is True: res[classname + "." + traitname].append(flag) diff --git a/contrib/python/traitlets/py3/traitlets/config/tests/test_application.py b/contrib/python/traitlets/py3/traitlets/config/tests/test_application.py index 62585aa29c..084ff6f032 100644 --- a/contrib/python/traitlets/py3/traitlets/config/tests/test_application.py +++ b/contrib/python/traitlets/py3/traitlets/config/tests/test_application.py @@ -34,7 +34,6 @@ pjoin = os.path.join class Foo(Configurable): - i = Integer( 0, help=""" @@ -51,7 +50,6 @@ class Foo(Configurable): class Bar(Configurable): - b = Integer(0, help="The integer b.").tag(config=True) enabled = Bool(True, help="Enable bar.").tag(config=True) tb = Tuple(()).tag(config=True, multiplicity="*") @@ -62,7 +60,6 @@ class Bar(Configurable): class MyApp(Application): - name = Unicode("myapp") running = Bool(False, help="Is the app running?").tag(config=True) classes = List([Bar, Foo]) # type:ignore @@ -475,7 +472,6 @@ class TestApplication(TestCase): self.assertEqual(app.config.MyApp.log_level, "CRITICAL") def test_extra_args(self): - app = MyApp() app.parse_command_line(["--Bar.b=5", "extra", "args", "--disable"]) app.init_bar() diff --git a/contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py b/contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py index 1769976601..357aede78a 100644 --- a/contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py +++ b/contrib/python/traitlets/py3/traitlets/config/tests/test_configurable.py @@ -22,9 +22,9 @@ from traitlets.traitlets import ( List, Set, Unicode, - _deprecations_shown, validate, ) +from traitlets.utils.warnings import _deprecations_shown from traitlets.tests._warnings import expected_warnings diff --git a/contrib/python/traitlets/py3/traitlets/config/tests/test_loader.py b/contrib/python/traitlets/py3/traitlets/config/tests/test_loader.py index 7355544c77..3a1f96120f 100644 --- a/contrib/python/traitlets/py3/traitlets/config/tests/test_loader.py +++ b/contrib/python/traitlets/py3/traitlets/config/tests/test_loader.py @@ -96,7 +96,6 @@ class TestFileCL(TestCase): self._check_conf(config) def test_context_manager(self): - fd, fname = mkstemp(".json", prefix="μnïcø∂e") f = os.fdopen(fd, "w") f.write("{}") diff --git a/contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py b/contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py index c99e9d2341..dab1f6ddc7 100644 --- a/contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py +++ b/contrib/python/traitlets/py3/traitlets/tests/test_traitlets.py @@ -112,7 +112,7 @@ class TestTraitType(TestCase): self.assertEqual(a._notify_new, 10) def test_validate(self): - class MyTT(TraitType): + class MyTT(TraitType[int, int]): def validate(self, inst, value): return -1 @@ -127,7 +127,7 @@ class TestTraitType(TestCase): self.assertEqual(a.tt, -1) def test_default_validate(self): - class MyIntTT(TraitType): + class MyIntTT(TraitType[int, int]): def validate(self, obj, value): if isinstance(value, int): return value @@ -154,7 +154,7 @@ class TestTraitType(TestCase): def test_error(self): class A(HasTraits): - tt = TraitType() + tt = TraitType[int, int]() a = A() self.assertRaises(TraitError, A.tt.error, a, 10) @@ -193,7 +193,6 @@ class TestTraitType(TestCase): self.assertEqual(a._trait_values, {"x": 11}) def test_deprecated_method_warnings(self): - with expected_warnings([]): class ShouldntWarn(HasTraits): @@ -271,14 +270,14 @@ class TestTraitType(TestCase): self.assertEqual(a._trait_values, {"x": 11}) def test_tag_metadata(self): - class MyIntTT(TraitType): + class MyIntTT(TraitType[int, int]): metadata = {"a": 1, "b": 2} a = MyIntTT(10).tag(b=3, c=4) self.assertEqual(a.metadata, {"a": 1, "b": 3, "c": 4}) def test_metadata_localized_instance(self): - class MyIntTT(TraitType): + class MyIntTT(TraitType[int, int]): metadata = {"a": 1, "b": 2} a = MyIntTT(10) @@ -326,7 +325,7 @@ class TestTraitType(TestCase): self.assertEqual(Foo().bar, {}) def test_deprecated_metadata_access(self): - class MyIntTT(TraitType): + class MyIntTT(TraitType[int, int]): metadata = {"a": 1, "b": 2} a = MyIntTT(10) @@ -395,12 +394,12 @@ class TestHasDescriptorsMeta(TestCase): def test_this_class(self): class A(HasTraits): - t = This() - tt = This() + t = This["A"]() + tt = This["A"]() class B(A): - tt = This() - ttt = This() + tt = This["A"]() + ttt = This["A"]() self.assertEqual(A.t.this_class, A) self.assertEqual(B.t.this_class, A) @@ -415,7 +414,6 @@ class TestHasDescriptors(TestCase): foo = inst.foo # instance should have the attr class HasFooDescriptors(HasDescriptors): - fd = FooDescriptor() def setup_instance(self, *args, **kwargs): @@ -597,7 +595,6 @@ class TestHasTraitsNotify(TestCase): self.b += 1 class B(A): - c = 0 d = 0 @@ -788,7 +785,6 @@ class TestObserveDecorator(TestCase): self.b += 1 class B(A): - c = 0 d = 0 @@ -1098,7 +1094,7 @@ class TestInstance(TestCase): class Bah: pass - class FooInstance(Instance): + class FooInstance(Instance[Foo]): klass = Foo class A(HasTraits): @@ -1174,7 +1170,7 @@ class TestInstance(TestCase): def inner(): class A(HasTraits): - inst = Instance(Foo()) + inst = Instance(Foo()) # type:ignore self.assertRaises(TraitError, inner) @@ -1182,7 +1178,7 @@ class TestInstance(TestCase): class TestThis(TestCase): def test_this_class(self): class Foo(HasTraits): - this = This() + this = This["Foo"]() f = Foo() self.assertEqual(f.this, None) @@ -1193,7 +1189,7 @@ class TestThis(TestCase): def test_this_inst(self): class Foo(HasTraits): - this = This() + this = This["Foo"]() f = Foo() f.this = Foo() @@ -1201,7 +1197,7 @@ class TestThis(TestCase): def test_subclass(self): class Foo(HasTraits): - t = This() + t = This["Foo"]() class Bar(Foo): pass @@ -1215,7 +1211,7 @@ class TestThis(TestCase): def test_subclass_override(self): class Foo(HasTraits): - t = This() + t = This["Foo"]() class Bar(Foo): t = This() @@ -1291,12 +1287,10 @@ class TraitTestBase(TestCase): class AnyTrait(HasTraits): - value = Any() class AnyTraitTest(TraitTestBase): - obj = AnyTrait() _default_value = None @@ -1305,48 +1299,40 @@ class AnyTraitTest(TraitTestBase): class UnionTrait(HasTraits): - value = Union([Type(), Bool()]) class UnionTraitTest(TraitTestBase): - obj = UnionTrait(value="traitlets.config.Config") _good_values = [int, float, True] _bad_values = [[], (0,), 1j] class CallableTrait(HasTraits): - value = Callable() class CallableTraitTest(TraitTestBase): - obj = CallableTrait(value=lambda x: type(x)) _good_values = [int, sorted, lambda x: print(x)] _bad_values = [[], 1, ""] class OrTrait(HasTraits): - value = Bool() | Unicode() class OrTraitTest(TraitTestBase): - obj = OrTrait() _good_values = [True, False, "ten"] _bad_values = [[], (0,), 1j] class IntTrait(HasTraits): - value = Int(99, min=-100) class TestInt(TraitTestBase): - obj = IntTrait() _default_value = 99 _good_values = [10, -10] @@ -1397,12 +1383,10 @@ class TestMinBoundCInt(TestCInt): class LongTrait(HasTraits): - value = Long(99) class TestLong(TraitTestBase): - obj = LongTrait() _default_value = 99 @@ -1513,12 +1497,10 @@ class TestMaxBoundInteger(TraitTestBase): class FloatTrait(HasTraits): - value = Float(99.0, max=200.0) class TestFloat(TraitTestBase): - obj = FloatTrait() _default_value = 99.0 @@ -1541,12 +1523,10 @@ class TestFloat(TraitTestBase): class CFloatTrait(HasTraits): - value = CFloat("99.0", max=200.0) class TestCFloat(TraitTestBase): - obj = CFloatTrait() _default_value = 99.0 @@ -1558,12 +1538,10 @@ class TestCFloat(TraitTestBase): class ComplexTrait(HasTraits): - value = Complex(99.0 - 99.0j) class TestComplex(TraitTestBase): - obj = ComplexTrait() _default_value = 99.0 - 99.0j @@ -1583,12 +1561,10 @@ class TestComplex(TraitTestBase): class BytesTrait(HasTraits): - value = Bytes(b"string") class TestBytes(TraitTestBase): - obj = BytesTrait() _default_value = b"string" @@ -1597,12 +1573,10 @@ class TestBytes(TraitTestBase): class UnicodeTrait(HasTraits): - value = Unicode("unicode") class TestUnicode(TraitTestBase): - obj = UnicodeTrait() _default_value = "unicode" @@ -1659,7 +1633,6 @@ class TCPAddressTrait(HasTraits): class TestTCPAddress(TraitTestBase): - obj = TCPAddressTrait() _default_value = ("127.0.0.1", 0) @@ -1668,12 +1641,10 @@ class TestTCPAddress(TraitTestBase): class ListTrait(HasTraits): - value = List(Int()) class TestList(TraitTestBase): - obj = ListTrait() _default_value: t.List[t.Any] = [] @@ -1691,12 +1662,10 @@ class Foo: class NoneInstanceListTrait(HasTraits): - value = List(Instance(Foo)) class TestNoneInstanceList(TraitTestBase): - obj = NoneInstanceListTrait() _default_value: t.List[t.Any] = [] @@ -1705,12 +1674,10 @@ class TestNoneInstanceList(TraitTestBase): class InstanceListTrait(HasTraits): - value = List(Instance(__name__ + ".Foo")) class TestInstanceList(TraitTestBase): - obj = InstanceListTrait() def test_klass(self): @@ -1731,12 +1698,10 @@ class TestInstanceList(TraitTestBase): class UnionListTrait(HasTraits): - value = List(Int() | Bool()) class TestUnionListTrait(TraitTestBase): - obj = UnionListTrait() _default_value: t.List[t.Any] = [] @@ -1745,12 +1710,10 @@ class TestUnionListTrait(TraitTestBase): class LenListTrait(HasTraits): - value = List(Int(), [0], minlen=1, maxlen=2) class TestLenList(TraitTestBase): - obj = LenListTrait() _default_value = [0] @@ -1764,12 +1727,10 @@ class TestLenList(TraitTestBase): class TupleTrait(HasTraits): - value = Tuple(Int(allow_none=True), default_value=(1,)) class TestTupleTrait(TraitTestBase): - obj = TupleTrait() _default_value = (1,) @@ -1788,12 +1749,10 @@ class TestTupleTrait(TraitTestBase): class LooseTupleTrait(HasTraits): - value = Tuple((1, 2, 3)) class TestLooseTupleTrait(TraitTestBase): - obj = LooseTupleTrait() _default_value = (1, 2, 3) @@ -1812,12 +1771,10 @@ class TestLooseTupleTrait(TraitTestBase): class MultiTupleTrait(HasTraits): - value = Tuple(Int(), Bytes(), default_value=[99, b"bottles"]) class TestMultiTuple(TraitTestBase): - obj = MultiTupleTrait() _default_value = (99, b"bottles") @@ -1884,7 +1841,6 @@ def test_subclass_default_value(Trait, default_value): class CRegExpTrait(HasTraits): - value = CRegExp(r"") @@ -1913,12 +1869,10 @@ def test_dict_assignment(): class UniformlyValueValidatedDictTrait(HasTraits): - value = Dict(value_trait=Unicode(), default_value={"foo": "1"}) class TestInstanceUniformlyValueValidatedDict(TraitTestBase): - obj = UniformlyValueValidatedDictTrait() _default_value = {"foo": "1"} @@ -1927,12 +1881,10 @@ class TestInstanceUniformlyValueValidatedDict(TraitTestBase): class NonuniformlyValueValidatedDictTrait(HasTraits): - value = Dict(per_key_traits={"foo": Int()}, default_value={"foo": 1}) class TestInstanceNonuniformlyValueValidatedDict(TraitTestBase): - obj = NonuniformlyValueValidatedDictTrait() _default_value = {"foo": 1} @@ -1941,12 +1893,10 @@ class TestInstanceNonuniformlyValueValidatedDict(TraitTestBase): class KeyValidatedDictTrait(HasTraits): - value = Dict(key_trait=Unicode(), default_value={"foo": "1"}) class TestInstanceKeyValidatedDict(TraitTestBase): - obj = KeyValidatedDictTrait() _default_value = {"foo": "1"} @@ -1955,7 +1905,6 @@ class TestInstanceKeyValidatedDict(TraitTestBase): class FullyValidatedDictTrait(HasTraits): - value = Dict( value_trait=Unicode(), key_trait=Unicode(), @@ -1965,7 +1914,6 @@ class FullyValidatedDictTrait(HasTraits): class TestInstanceFullyValidatedDict(TraitTestBase): - obj = FullyValidatedDictTrait() _default_value = {"foo": 1} @@ -1992,7 +1940,6 @@ class TestValidationHook(TestCase): """Verify that the early validation hook is effective""" class Parity(HasTraits): - value = Int(0) parity = Enum(["odd", "even"], default_value="even") @@ -2018,7 +1965,6 @@ class TestValidationHook(TestCase): """Verify that we can register the same validator to multiple names""" class OddEven(HasTraits): - odd = Int(1) even = Int(0) @@ -2319,7 +2265,6 @@ class TestDirectionalLink(TestCase): class Pickleable(HasTraits): - i = Int() @observe("i") @@ -2478,22 +2423,18 @@ def test_notification_order(): # Traits for Forward Declaration Tests ### class ForwardDeclaredInstanceTrait(HasTraits): - - value = ForwardDeclaredInstance("ForwardDeclaredBar", allow_none=True) + value = ForwardDeclaredInstance["ForwardDeclaredBar"]("ForwardDeclaredBar", allow_none=True) class ForwardDeclaredTypeTrait(HasTraits): - - value = ForwardDeclaredType("ForwardDeclaredBar", allow_none=True) + value = ForwardDeclaredType[t.Any, t.Any]("ForwardDeclaredBar", allow_none=True) class ForwardDeclaredInstanceListTrait(HasTraits): - value = List(ForwardDeclaredInstance("ForwardDeclaredBar")) class ForwardDeclaredTypeListTrait(HasTraits): - value = List(ForwardDeclaredType("ForwardDeclaredBar")) @@ -2501,6 +2442,7 @@ class ForwardDeclaredTypeListTrait(HasTraits): # End Traits for Forward Declaration Tests ### + ### # Classes for Forward Declaration Tests ### @@ -2516,11 +2458,11 @@ class ForwardDeclaredBarSub(ForwardDeclaredBar): # End Classes for Forward Declaration Tests ### + ### # Forward Declaration Tests ### class TestForwardDeclaredInstanceTrait(TraitTestBase): - obj = ForwardDeclaredInstanceTrait() _default_value = None _good_values = [None, ForwardDeclaredBar(), ForwardDeclaredBarSub()] @@ -2528,7 +2470,6 @@ class TestForwardDeclaredInstanceTrait(TraitTestBase): class TestForwardDeclaredTypeTrait(TraitTestBase): - obj = ForwardDeclaredTypeTrait() _default_value = None _good_values = [None, ForwardDeclaredBar, ForwardDeclaredBarSub] @@ -2536,7 +2477,6 @@ class TestForwardDeclaredTypeTrait(TraitTestBase): class TestForwardDeclaredInstanceList(TraitTestBase): - obj = ForwardDeclaredInstanceListTrait() def test_klass(self): @@ -2560,7 +2500,6 @@ class TestForwardDeclaredInstanceList(TraitTestBase): class TestForwardDeclaredTypeList(TraitTestBase): - obj = ForwardDeclaredTypeListTrait() def test_klass(self): @@ -2673,7 +2612,6 @@ def test_default_value_repr(): class TransitionalClass(HasTraits): - d = Any() @default("d") diff --git a/contrib/python/traitlets/py3/traitlets/tests/utils.py b/contrib/python/traitlets/py3/traitlets/tests/utils.py index 50360a372f..c39d86942a 100644 --- a/contrib/python/traitlets/py3/traitlets/tests/utils.py +++ b/contrib/python/traitlets/py3/traitlets/tests/utils.py @@ -7,7 +7,7 @@ def get_output_error_code(cmd): """Get stdout, stderr, and exit code from running a command""" env = os.environ.copy() env["Y_PYTHON_ENTRY_POINT"] = ":main" - p = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env) + p = Popen(cmd, stdout=PIPE, stderr=PIPE, env=env) # noqa out, err = p.communicate() out = out.decode("utf8", "replace") # type:ignore err = err.decode("utf8", "replace") # type:ignore diff --git a/contrib/python/traitlets/py3/traitlets/traitlets.py b/contrib/python/traitlets/py3/traitlets/traitlets.py index 7cf6916856..8e25f5bda5 100644 --- a/contrib/python/traitlets/py3/traitlets/traitlets.py +++ b/contrib/python/traitlets/py3/traitlets/traitlets.py @@ -38,6 +38,7 @@ Inheritance diagram: # # Adapted from enthought.traits, Copyright (c) Enthought, Inc., # also under the terms of the Modified BSD License. +from __future__ import annotations import contextlib import enum @@ -48,13 +49,13 @@ import sys import types import typing as t from ast import literal_eval -from warnings import warn, warn_explicit from .utils.bunch import Bunch from .utils.descriptions import add_article, class_of, describe, repr_type from .utils.getargspec import getargspec from .utils.importstring import import_item from .utils.sentinel import Sentinel +from .utils.warnings import deprecated_method, should_warn, warn SequenceTypes = (list, tuple, set, frozenset) @@ -161,61 +162,11 @@ class TraitError(Exception): # Utilities # ----------------------------------------------------------------------------- -_name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$") - def isidentifier(s): return s.isidentifier() -_deprecations_shown = set() - - -def _should_warn(key): - """Add our own checks for too many deprecation warnings. - - Limit to once per package. - """ - env_flag = os.environ.get("TRAITLETS_ALL_DEPRECATIONS") - if env_flag and env_flag != "0": - return True - - if key not in _deprecations_shown: - _deprecations_shown.add(key) - return True - else: - return False - - -def _deprecated_method(method, cls, method_name, msg): - """Show deprecation warning about a magic method definition. - - Uses warn_explicit to bind warning to method definition instead of triggering code, - which isn't relevant. - """ - warn_msg = "{classname}.{method_name} is deprecated in traitlets 4.1: {msg}".format( - classname=cls.__name__, method_name=method_name, msg=msg - ) - - for parent in inspect.getmro(cls): - if method_name in parent.__dict__: - cls = parent - break - # limit deprecation messages to once per package - package_name = cls.__module__.split(".", 1)[0] - key = (package_name, msg) - if not _should_warn(key): - return - try: - fname = inspect.getsourcefile(method) or "<unknown>" - lineno = inspect.getsourcelines(method)[1] or 0 - except (OSError, TypeError) as e: - # Failed to inspect for some reason - warn(warn_msg + ("\n(inspection failed) %s" % e), DeprecationWarning) - else: - warn_explicit(warn_msg, DeprecationWarning, fname, lineno) - - def _safe_literal_eval(s): """Safely evaluate an expression @@ -234,7 +185,7 @@ def is_trait(t): return isinstance(t, TraitType) or (isinstance(t, type) and issubclass(t, TraitType)) -def parse_notifier_name(names): +def parse_notifier_name(names: Sentinel | str | t.Iterable[Sentinel | str]) -> t.Iterable[t.Any]: """Convert the name argument to a list of names. Examples @@ -250,12 +201,14 @@ def parse_notifier_name(names): """ if names is All or isinstance(names, str): return [names] + elif isinstance(names, Sentinel): + raise TypeError("`names` must be either `All`, a str, or a list of strs.") else: if not names or All in names: return [All] for n in names: if not isinstance(n, str): - raise TypeError("names must be strings, not %r" % n) + raise TypeError(f"names must be strings, not {type(n).__name__}({n!r})") return names @@ -368,8 +321,7 @@ class link: setattr(self.target[0], self.target[1], self._transform(change.new)) if getattr(self.source[0], self.source[1]) != change.new: raise TraitError( - "Broken link {}: the source value changed while updating " - "the target.".format(self) + f"Broken link {self}: the source value changed while updating " "the target." ) def _update_source(self, change): @@ -379,8 +331,7 @@ class link: setattr(self.source[0], self.source[1], self._transform_inv(change.new)) if getattr(self.target[0], self.target[1]) != change.new: raise TraitError( - "Broken link {}: the target value changed while updating " - "the source.".format(self) + f"Broken link {self}: the target value changed while updating " "the source." ) def unlink(self): @@ -482,8 +433,8 @@ class BaseDescriptor: accept superclasses for :class:`This` values. """ - name: t.Optional[str] = None - this_class: t.Optional[t.Type[t.Any]] = None + name: str | None = None + this_class: type[t.Any] | None = None def class_init(self, cls, name): """Part of the initialization which may depend on the underlying @@ -522,23 +473,37 @@ class BaseDescriptor: pass -class TraitType(BaseDescriptor): +G = t.TypeVar("G") +S = t.TypeVar("S") +T = t.TypeVar("T") + + +# Self from typing extension doesn't work well with mypy https://github.com/python/mypy/pull/14041 +# see https://peps.python.org/pep-0673/#use-in-generic-classes +# Self = t.TypeVar("Self", bound="TraitType[Any, Any]") +if t.TYPE_CHECKING: + from typing_extensions import Literal, Self + + +# We use a type for the getter (G) and setter (G) because we allow +# for traits to cast (for instance CInt will use G=int, S=t.Any) +class TraitType(BaseDescriptor, t.Generic[G, S]): """A base class for all trait types.""" - metadata: t.Dict[str, t.Any] = {} - allow_none = False - read_only = False - info_text = "any value" - default_value: t.Optional[t.Any] = Undefined + metadata: dict[str, t.Any] = {} + allow_none: bool = False + read_only: bool = False + info_text: str = "any value" + default_value: t.Any | None = Undefined def __init__( - self, - default_value=Undefined, - allow_none=False, - read_only=None, - help=None, - config=None, - **kwargs, + self: TraitType[G, S], + default_value: t.Any = Undefined, + allow_none: bool = False, + read_only: bool | None = None, + help: str | None = None, + config: t.Any = None, + **kwargs: t.Any, ): """Declare a traitlet. @@ -548,6 +513,8 @@ class TraitType(BaseDescriptor): If *read_only* is True, attempts to directly modify a trait attribute raises a TraitError. + If *help* is a string, it documents the attribute's purpose. + Extra metadata can be associated with the traitlet using the .tag() convenience method or by using the traitlet instance's .metadata dictionary. """ @@ -573,12 +540,12 @@ class TraitType(BaseDescriptor): assert f is not None mod = f.f_globals.get("__name__") or "" pkg = mod.split(".", 1)[0] - key = tuple(["metadata-tag", pkg] + sorted(kwargs)) - if _should_warn(key): + key = ("metadata-tag", pkg, *sorted(kwargs)) + if should_warn(key): warn( - "metadata %s was set from the constructor. " + f"metadata {kwargs} was set from the constructor. " "With traitlets 4.1, metadata should be set using the .tag() method, " - "e.g., Int().tag(key1='value1', key2='value2')" % (kwargs,), + "e.g., Int().tag(key1='value1', key2='value2')", DeprecationWarning, stacklevel=stacklevel, ) @@ -651,9 +618,9 @@ class TraitType(BaseDescriptor): obj._trait_values[self.name] = value return value - def get(self, obj, cls=None): + def get(self, obj: HasTraits, cls: t.Any = None) -> G | None: try: - value = obj._trait_values[self.name] + value = obj._trait_values[self.name] # type: ignore except KeyError: # Check for a dynamic initializer. default = obj.trait_defaults(self.name) @@ -673,7 +640,7 @@ class TraitType(BaseDescriptor): value = self._validate(obj, default) finally: obj._cross_validation_lock = _cross_validation_lock - obj._trait_values[self.name] = value + obj._trait_values[self.name] = value # type: ignore obj._notify_observers( Bunch( name=self.name, @@ -682,14 +649,60 @@ class TraitType(BaseDescriptor): type="default", ) ) - return value + return value # type: ignore except Exception as e: # This should never be reached. raise TraitError("Unexpected error in TraitType: default value not set properly") from e else: - return value + return value # type: ignore + + if t.TYPE_CHECKING: + # This gives ok type information, but not specific enough (e.g. it will) + # always be a TraitType, not a subclass, like Bool. + @t.overload + def __new__( # type: ignore[misc] + cls, + default_value: S | Sentinel = Undefined, + allow_none: Literal[False] = ..., + read_only: bool | None = None, + help: str | None = None, + config: t.Any = None, + **kwargs: t.Any, + ) -> TraitType[G, S]: + ... + + @t.overload + def __new__( + cls, + default_value: S | None | Sentinel = Undefined, + allow_none: Literal[True] = ..., + read_only: bool | None = None, + help: str | None = None, + config: t.Any = None, + **kwargs: t.Any, + ) -> TraitType[G | None, S]: + ... + + def __new__( # type: ignore[no-untyped-def, misc] + cls, + default_value: S | None | Sentinel = Undefined, + allow_none: Literal[True, False] = False, + read_only=None, + help=None, + config=None, + **kwargs, + ) -> TraitType[G | None, S] | TraitType[G, S]: + ... + + @t.overload + def __get__(self, obj: None, cls: type[t.Any]) -> Self: + ... - def __get__(self, obj, cls=None): + @t.overload + def __get__(self, obj: t.Any, cls: type[t.Any]) -> G: + ... + + def __get__(self, obj: HasTraits | None, cls: type[t.Any]) -> Self | G: """Get the value of the trait by self.name for the instance. Default values are instantiated when :meth:`HasTraits.__new__` @@ -700,7 +713,7 @@ class TraitType(BaseDescriptor): if obj is None: return self else: - return self.get(obj, cls) + return t.cast(G, self.get(obj, cls)) # the G should encode the Optional def set(self, obj, value): new_value = self._validate(obj, value) @@ -720,7 +733,7 @@ class TraitType(BaseDescriptor): # comparison above returns something other than True/False obj._notify_trait(self.name, old_value, new_value) - def __set__(self, obj, value): + def __set__(self, obj: HasTraits, value: S) -> None: """Set the value of the trait by self.name for the instance. Values pass through a validation stage where errors are raised when @@ -747,7 +760,7 @@ class TraitType(BaseDescriptor): elif hasattr(obj, "_%s_validate" % self.name): meth_name = "_%s_validate" % self.name cross_validate = getattr(obj, meth_name) - _deprecated_method( + deprecated_method( cross_validate, obj.__class__, meth_name, @@ -758,7 +771,7 @@ class TraitType(BaseDescriptor): def __or__(self, other): if isinstance(other, Union): - return Union([self] + other.trait_types) + return Union([self, *other.trait_types]) else: return Union([self, other]) @@ -799,9 +812,8 @@ class TraitType(BaseDescriptor): chain = " of ".join(describe("a", t) for t in error.args[2:]) if obj is not None: error.args = ( - "The '%s' trait of %s instance contains %s which " - "expected %s, not %s." - % ( + "The '{}' trait of {} instance contains {} which " + "expected {}, not {}.".format( self.name, describe("an", obj), chain, @@ -811,9 +823,8 @@ class TraitType(BaseDescriptor): ) else: error.args = ( - "The '%s' trait contains %s which " - "expected %s, not %s." - % ( + "The '{}' trait contains {} which " + "expected {}, not {}.".format( self.name, chain, error.args[1], @@ -867,7 +878,7 @@ class TraitType(BaseDescriptor): warn("Deprecated in traitlets 4.1, " + msg, DeprecationWarning, stacklevel=2) self.metadata[key] = value - def tag(self, **metadata): + def tag(self, **metadata: t.Any) -> Self: """Sets metadata and returns self. This allows convenient metadata tagging when initializing the trait, such as: @@ -1082,7 +1093,7 @@ class MetaHasTraits(MetaHasDescriptors): cls._all_trait_default_generators[name] = trait.default -def observe(*names: t.Union[Sentinel, str], type: str = "change") -> "ObserveHandler": +def observe(*names: Sentinel | str, type: str = "change") -> ObserveHandler: """A decorator which can be used to observe Traits on a class. The handler passed to the decorator will be called with one ``change`` @@ -1132,9 +1143,9 @@ def observe_compat(func): else: clsname = self.__class__.__name__ warn( - "A parent of %s._%s_changed has adopted the new (traitlets 4.1) @observe(change) API" - % (clsname, change_or_name), + f"A parent of {clsname}._{change_or_name}_changed has adopted the new (traitlets 4.1) @observe(change) API", DeprecationWarning, + stacklevel=2, ) change = Bunch( type="change", @@ -1148,7 +1159,7 @@ def observe_compat(func): return compatible_observer -def validate(*names: t.Union[Sentinel, str]) -> "ValidateHandler": +def validate(*names: Sentinel | str) -> ValidateHandler: """A decorator to register cross validator of HasTraits object's state when a Trait is set. @@ -1181,7 +1192,7 @@ def validate(*names: t.Union[Sentinel, str]) -> "ValidateHandler": return ValidateHandler(names) -def default(name: str) -> "DefaultHandler": +def default(name: str) -> DefaultHandler: """A decorator which assigns a dynamic default for a Trait on a HasTraits object. Parameters @@ -1304,13 +1315,13 @@ class HasDescriptors(metaclass=MetaHasDescriptors): class HasTraits(HasDescriptors, metaclass=MetaHasTraits): - _trait_values: t.Dict[str, t.Any] - _static_immutable_initial_values: t.Dict[str, t.Any] - _trait_notifiers: t.Dict[str, t.Any] - _trait_validators: t.Dict[str, t.Any] + _trait_values: dict[str, t.Any] + _static_immutable_initial_values: dict[str, t.Any] + _trait_notifiers: dict[str, t.Any] + _trait_validators: dict[str, t.Any] _cross_validation_lock: bool - _traits: t.Dict[str, t.Any] - _all_trait_default_generators: t.Dict[str, t.Any] + _traits: dict[str, t.Any] + _all_trait_default_generators: dict[str, t.Any] def setup_instance(*args, **kwargs): # Pass self as args[0] to allow "self" as keyword argument @@ -1339,7 +1350,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): def ignore(*_ignore_args): pass - self.notify_change = ignore # type:ignore[assignment] + self.notify_change = ignore # type:ignore[method-assign] self._cross_validation_lock = True changes = {} for key, value in kwargs.items(): @@ -1449,7 +1460,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): yield return else: - cache: t.Dict[str, t.Any] = {} + cache: dict[str, t.Any] = {} def compress(past_changes, change): """Merges the provided change with the last if possible.""" @@ -1470,7 +1481,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): try: # Replace notify_change with `hold`, caching and compressing # notifications, disable cross validation and yield. - self.notify_change = hold # type:ignore[assignment] + self.notify_change = hold # type:ignore[method-assign] self._cross_validation_lock = True yield # Cross validate final values when context is released. @@ -1480,7 +1491,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): self.set_trait(name, value) except TraitError as e: # Roll back in case of TraitError during final cross validation. - self.notify_change = lambda x: None # type:ignore[assignment] + self.notify_change = lambda x: None # type:ignore[method-assign] for name, changes in cache.items(): for change in changes[::-1]: # TODO: Separate in a rollback function per notification type. @@ -1540,7 +1551,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): if event['type'] == "change" and hasattr(self, magic_name): class_value = getattr(self.__class__, magic_name) if not isinstance(class_value, ObserveHandler): - _deprecated_method( + deprecated_method( class_value, self.__class__, magic_name, @@ -1565,7 +1576,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): def _add_notifiers(self, handler, name, type): if name not in self._trait_notifiers: - nlist: t.List[t.Any] = [] + nlist: list[t.Any] = [] self._trait_notifiers[name] = {type: nlist} else: if type not in self._trait_notifiers[name]: @@ -1625,7 +1636,12 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): else: self.observe(_callback_wrapper(handler), names=name) - def observe(self, handler, names=All, type="change"): + def observe( + self, + handler: t.Callable[..., t.Any], + names: Sentinel | str | t.Iterable[Sentinel | str] = All, + type: Sentinel | str = "change", + ) -> None: """Setup a handler to be called when a trait changes. This is used to setup dynamic notifications of trait changes. @@ -1651,11 +1667,15 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): The type of notification to filter by. If equal to All, then all notifications are passed to the observe handler. """ - names = parse_notifier_name(names) - for n in names: - self._add_notifiers(handler, n, type) + for name in parse_notifier_name(names): + self._add_notifiers(handler, name, type) - def unobserve(self, handler, names=All, type="change"): + def unobserve( + self, + handler: t.Callable[..., t.Any], + names: Sentinel | str | t.Iterable[Sentinel | str] = All, + type: Sentinel | str = "change", + ) -> None: """Remove a trait change handler. This is used to unregister handlers to trait change notifications. @@ -1672,15 +1692,14 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): The type of notification to filter by. If All, the specified handler is uninstalled from the list of notifiers corresponding to all types. """ - names = parse_notifier_name(names) - for n in names: - self._remove_notifiers(handler, n, type) + for name in parse_notifier_name(names): + self._remove_notifiers(handler, name, type) - def unobserve_all(self, name=All): + def unobserve_all(self, name: str | t.Any = All) -> None: """Remove trait change handlers of any type for the specified name. If name is not specified, removes all trait notifiers.""" if name is All: - self._trait_notifiers: t.Dict[str, t.Any] = {} + self._trait_notifiers: dict[str, t.Any] = {} else: try: del self._trait_notifiers[name] @@ -1712,7 +1731,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): if hasattr(self, magic_name): class_value = getattr(self.__class__, magic_name) if not isinstance(class_value, ValidateHandler): - _deprecated_method( + deprecated_method( class_value, self.__class__, magic_name, @@ -1977,7 +1996,7 @@ class HasTraits(HasDescriptors, metaclass=MetaHasTraits): # ----------------------------------------------------------------------------- -class ClassBasedTraitType(TraitType): +class ClassBasedTraitType(TraitType[G, S]): """ A trait with error reporting and string -> type resolution for Type, Instance and This. @@ -1990,10 +2009,64 @@ class ClassBasedTraitType(TraitType): return import_item(string) -class Type(ClassBasedTraitType): +class Type(ClassBasedTraitType[G, S]): """A trait whose value must be a subclass of a specified class.""" - def __init__(self, default_value=Undefined, klass=None, **kwargs): + if t.TYPE_CHECKING: + + @t.overload + def __init__( + self: Type[object, object], + default_value: Sentinel | None | str = ..., + klass: None | str = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: Type[object | None, object | None], + default_value: S | Sentinel | None | str = ..., + klass: None | str = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: Type[S, S], + default_value: S | Sentinel | str = ..., + klass: type[S] = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: Type[S | None, S | None], + default_value: S | Sentinel | None | str = ..., + klass: type[S] = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + def __init__(self, default_value=Undefined, klass=None, allow_none=False, **kwargs): """Construct a Type trait A Type trait specifies that its values must be subclasses of @@ -2035,7 +2108,7 @@ class Type(ClassBasedTraitType): self.klass = klass - super().__init__(new_default_value, **kwargs) + super().__init__(new_default_value, allow_none=allow_none, **kwargs) def validate(self, obj, value): """Validates that the value is a valid object instance.""" @@ -2044,8 +2117,8 @@ class Type(ClassBasedTraitType): value = self._resolve_string(value) except ImportError as e: raise TraitError( - "The '%s' trait of %s instance must be a type, but " - "%r could not be imported" % (self.name, obj, value) + f"The '{self.name}' trait of {obj} instance must be a type, but " + f"{value!r} could not be imported" ) from e try: if issubclass(value, self.klass): # type:ignore[arg-type] @@ -2086,7 +2159,7 @@ class Type(ClassBasedTraitType): return repr(f"{value.__module__}.{value.__name__}") -class Instance(ClassBasedTraitType): +class Instance(ClassBasedTraitType[T, T]): """A trait whose value must be an instance of a specified class. The value can also be an instance of a subclass of the specified class. @@ -2094,9 +2167,72 @@ class Instance(ClassBasedTraitType): Subclasses can declare default classes by overriding the klass attribute """ - klass = None + klass: str | type[T] | None = None + + if t.TYPE_CHECKING: + + @t.overload + def __init__( + self: Instance[T], + klass: type[T] = ..., + args: tuple[t.Any, ...] | None = ..., + kw: dict[str, t.Any] | None = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + **kwargs: t.Any, + ) -> None: + ... + + @t.overload + def __init__( + self: Instance[T | None], + klass: type[T] = ..., + args: tuple[t.Any, ...] | None = ..., + kw: dict[str, t.Any] | None = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + **kwargs: t.Any, + ) -> None: + ... + + @t.overload + def __init__( + self: Instance[t.Any], + klass: str | None = ..., + args: tuple[t.Any, ...] | None = ..., + kw: dict[str, t.Any] | None = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + **kwargs: t.Any, + ) -> None: + ... + + @t.overload + def __init__( + self: Instance[t.Any | None], + klass: str | None = ..., + args: tuple[t.Any, ...] | None = ..., + kw: dict[str, t.Any] | None = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + **kwargs: t.Any, + ) -> None: + ... - def __init__(self, klass=None, args=None, kw=None, **kwargs): + def __init__( + self, + klass: str | type[T] | None = None, + args: tuple[t.Any, ...] | None = None, + kw: dict[str, t.Any] | None = None, + allow_none: bool = False, + read_only: bool | None = None, + help: str | None = None, + **kwargs: t.Any, + ) -> None: """Construct an Instance trait. This trait allows values that are instances of a particular @@ -2141,7 +2277,7 @@ class Instance(ClassBasedTraitType): self.default_args = args self.default_kwargs = kw - super().__init__(**kwargs) + super().__init__(allow_none=allow_none, **kwargs) def validate(self, obj, value): assert self.klass is not None @@ -2197,7 +2333,7 @@ class ForwardDeclaredMixin: return import_item(".".join([modname, string])) -class ForwardDeclaredType(ForwardDeclaredMixin, Type): +class ForwardDeclaredType(ForwardDeclaredMixin, Type[G, S]): """ Forward-declared version of Type. """ @@ -2205,7 +2341,7 @@ class ForwardDeclaredType(ForwardDeclaredMixin, Type): pass -class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance): +class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance[T]): """ Forward-declared version of Instance. """ @@ -2213,7 +2349,7 @@ class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance): pass -class This(ClassBasedTraitType): +class This(ClassBasedTraitType[t.Optional[T], t.Optional[T]]): """A trait for instances of the class containing this trait. Because how how and when class bodies are executed, the ``This`` @@ -2237,7 +2373,7 @@ class This(ClassBasedTraitType): self.error(obj, value) -class Union(TraitType): +class Union(TraitType[t.Any, t.Any]): """A trait type representing a Union type.""" def __init__(self, trait_types, **kwargs): @@ -2309,7 +2445,7 @@ class Union(TraitType): if isinstance(other, Union): return Union(self.trait_types + other.trait_types) else: - return Union(self.trait_types + [other]) + return Union([*self.trait_types, other]) def from_string(self, s): for trait_type in self.trait_types: @@ -2326,10 +2462,74 @@ class Union(TraitType): # ----------------------------------------------------------------------------- -class Any(TraitType): +class Any(TraitType[t.Optional[t.Any], t.Optional[t.Any]]): """A trait which allows any value.""" - default_value: t.Optional[t.Any] = None + if t.TYPE_CHECKING: + + @t.overload + def __init__( + self: Any, + default_value: str = ..., + *, + allow_none: Literal[False], + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: Any, + default_value: str = ..., + *, + allow_none: Literal[True], + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: Any, + default_value: str = ..., + *, + allow_none: Literal[True, False] = ..., + help: str | None = ..., + read_only: bool | None = False, + config: t.Any = None, + **kwargs: t.Any, + ): + ... + + def __init__( + self: Any, + default_value: str = ..., + *, + allow_none: bool | None = False, + help: str | None = "", + read_only: bool | None = False, + config: t.Any = None, + **kwargs: t.Any, + ): + ... + + @t.overload + def __get__(self, obj: None, cls: type[t.Any]) -> Any: + ... + + @t.overload + def __get__(self, obj: t.Any, cls: type[t.Any]) -> t.Any: + ... + + def __get__(self, obj: t.Any | None, cls: type[t.Any]) -> t.Any | Any: + ... + + default_value: t.Any | None = None allow_none = True info_text = "any value" @@ -2346,29 +2546,52 @@ def _validate_bounds(trait, obj, value): """ if trait.min is not None and value < trait.min: raise TraitError( - "The value of the '{name}' trait of {klass} instance should " - "not be less than {min_bound}, but a value of {value} was " - "specified".format( - name=trait.name, klass=class_of(obj), value=value, min_bound=trait.min - ) + f"The value of the '{trait.name}' trait of {class_of(obj)} instance should " + f"not be less than {trait.min}, but a value of {value} was " + "specified" ) if trait.max is not None and value > trait.max: raise TraitError( - "The value of the '{name}' trait of {klass} instance should " - "not be greater than {max_bound}, but a value of {value} was " - "specified".format( - name=trait.name, klass=class_of(obj), value=value, max_bound=trait.max - ) + f"The value of the '{trait.name}' trait of {class_of(obj)} instance should " + f"not be greater than {trait.max}, but a value of {value} was " + "specified" ) return value -class Int(TraitType): +# I = t.TypeVar('I', t.Optional[int], int) + + +class Int(TraitType[G, S]): """An int trait.""" default_value = 0 info_text = "an int" + @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, + ): + ... + + @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, + ): + ... + def __init__(self, default_value=Undefined, allow_none=False, **kwargs): self.min = kwargs.pop("min", None) self.max = kwargs.pop("max", None) @@ -2388,9 +2611,38 @@ class Int(TraitType): pass # fully opt out of instance_init -class CInt(Int): +class CInt(Int[G, S]): """A casting version of the int trait.""" + if t.TYPE_CHECKING: + + @t.overload + def __init__( + self: CInt[int, t.Any], + default_value: t.Any | Sentinel = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: CInt[int | None, t.Any], + default_value: t.Any | Sentinel | None = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + def __init__(self, default_value=Undefined, allow_none=False, **kwargs): + ... + def validate(self, obj, value): try: value = int(value) @@ -2403,12 +2655,36 @@ Long, CLong = Int, CInt Integer = Int -class Float(TraitType): +class Float(TraitType[G, S]): """A float trait.""" default_value = 0.0 info_text = "a float" + @t.overload + def __init__( + self: Float[float, int | float], + default_value: float | Sentinel = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: Float[int | None, int | float | None], + default_value: float | Sentinel | None = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + def __init__(self, default_value=Undefined, allow_none=False, **kwargs): self.min = kwargs.pop("min", -float("inf")) self.max = kwargs.pop("max", float("inf")) @@ -2430,9 +2706,38 @@ class Float(TraitType): pass # fully opt out of instance_init -class CFloat(Float): +class CFloat(Float[G, S]): """A casting version of the float trait.""" + if t.TYPE_CHECKING: + + @t.overload + def __init__( + self: CFloat[float, t.Any], + default_value: t.Any = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: CFloat[float | None, t.Any], + default_value: t.Any = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + def __init__(self, default_value=Undefined, allow_none=False, **kwargs): + ... + def validate(self, obj, value): try: value = float(value) @@ -2441,7 +2746,7 @@ class CFloat(Float): return _validate_bounds(self, obj, value) -class Complex(TraitType): +class Complex(TraitType[complex, t.Union[complex, float, int]]): """A trait for complex numbers.""" default_value = 0.0 + 0.0j @@ -2463,7 +2768,7 @@ class Complex(TraitType): pass # fully opt out of instance_init -class CComplex(Complex): +class CComplex(Complex, TraitType[complex, t.Any]): """A casting version of the complex number trait.""" def validate(self, obj, value): @@ -2476,7 +2781,7 @@ class CComplex(Complex): # We should always be explicit about whether we're using bytes or unicode, both # for Python 3 conversion and for reliable unicode behaviour on Python 2. So # we don't have a Str type. -class Bytes(TraitType): +class Bytes(TraitType[bytes, bytes]): """A trait for byte strings.""" default_value = b"" @@ -2498,8 +2803,9 @@ class Bytes(TraitType): s = s[2:-1] warn( "Supporting extra quotes around Bytes is deprecated in traitlets 5.0. " - "Use %r instead of %r." % (s, old_s), - FutureWarning, + f"Use {s!r} instead of {old_s!r}.", + DeprecationWarning, + stacklevel=2, ) break return s.encode("utf8") @@ -2508,7 +2814,7 @@ class Bytes(TraitType): pass # fully opt out of instance_init -class CBytes(Bytes): +class CBytes(Bytes, TraitType[bytes, t.Any]): """A casting version of the byte string trait.""" def validate(self, obj, value): @@ -2518,12 +2824,41 @@ class CBytes(Bytes): self.error(obj, value) -class Unicode(TraitType): +class Unicode(TraitType[G, S]): """A trait for unicode strings.""" default_value = "" info_text = "a unicode string" + if t.TYPE_CHECKING: + + @t.overload + def __init__( + self: Unicode[str, str | bytes], + default_value: str | Sentinel = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: Unicode[str | None, str | bytes | None], + default_value: str | Sentinel | None = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any = ..., + **kwargs: t.Any, + ): + ... + + def __init__(self, **kwargs): + ... + def validate(self, obj, value): if isinstance(value, str): return value @@ -2547,8 +2882,9 @@ class Unicode(TraitType): s = s[1:-1] warn( "Supporting extra quotes around strings is deprecated in traitlets 5.0. " - "You can use %r instead of %r if you require traitlets >=5." % (s, old_s), - FutureWarning, + f"You can use {s!r} instead of {old_s!r} if you require traitlets >=5.", + DeprecationWarning, + stacklevel=2, ) return s @@ -2556,9 +2892,38 @@ class Unicode(TraitType): pass # fully opt out of instance_init -class CUnicode(Unicode): +class CUnicode(Unicode[G, S], TraitType[str, t.Any]): """A casting version of the unicode trait.""" + if t.TYPE_CHECKING: + + @t.overload + def __init__( + self: CUnicode[str, t.Any], + default_value: str | Sentinel = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: CUnicode[str | None, t.Any], + default_value: str | Sentinel | None = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any = ..., + **kwargs: t.Any, + ): + ... + + def __init__(self, **kwargs): + ... + def validate(self, obj, value): try: return str(value) @@ -2566,14 +2931,14 @@ class CUnicode(Unicode): self.error(obj, value) -class ObjectName(TraitType): +class ObjectName(TraitType[str, str]): """A string holding a valid object name in this version of Python. This does not check that the name exists in any scope.""" info_text = "a valid object identifier in Python" - coerce_str = staticmethod(lambda _, s: s) # type:ignore[no-any-return] + coerce_str = staticmethod(lambda _, s: s) def validate(self, obj, value): value = self.coerce_str(obj, value) @@ -2599,12 +2964,41 @@ class DottedObjectName(ObjectName): self.error(obj, value) -class Bool(TraitType): +class Bool(TraitType[G, S]): """A boolean (True, False) trait.""" default_value = False info_text = "a boolean" + if t.TYPE_CHECKING: + + @t.overload + def __init__( + self: Bool[bool, bool | int], + default_value: bool | Sentinel = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: Bool[bool | None, bool | int | None], + default_value: bool | Sentinel | None = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any = ..., + **kwargs: t.Any, + ): + ... + + def __init__(self, **kwargs): + ... + def validate(self, obj, value): if isinstance(value, bool): return value @@ -2637,9 +3031,38 @@ class Bool(TraitType): return completions -class CBool(Bool): +class CBool(Bool[G, S]): """A casting version of the boolean trait.""" + if t.TYPE_CHECKING: + + @t.overload + def __init__( + self: CBool[bool, t.Any], + default_value: bool | Sentinel = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: CBool[bool | None, t.Any], + default_value: bool | Sentinel | None = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any = ..., + **kwargs: t.Any, + ): + ... + + def __init__(self, **kwargs): + ... + def validate(self, obj, value): try: return bool(value) @@ -2647,10 +3070,12 @@ class CBool(Bool): self.error(obj, value) -class Enum(TraitType): +class Enum(TraitType[G, S]): """An enum whose value must be in a given sequence.""" - def __init__(self, values, default_value=Undefined, **kwargs): + def __init__( + self: Enum[t.Any, t.Any], values: t.Any, default_value: t.Any = Undefined, **kwargs: t.Any + ): self.values = values if kwargs.get("allow_none", False) and default_value is Undefined: default_value = None @@ -2695,10 +3120,15 @@ class Enum(TraitType): return [str(v) for v in self.values] -class CaselessStrEnum(Enum): +class CaselessStrEnum(Enum[G, S]): """An enum of strings where the case should be ignored.""" - def __init__(self, values, default_value=Undefined, **kwargs): + def __init__( + self: CaselessStrEnum[t.Any, t.Any], + values: t.Any, + default_value: t.Any = Undefined, + **kwargs: t.Any, + ): super().__init__(values, default_value=default_value, **kwargs) def validate(self, obj, value): @@ -2722,7 +3152,7 @@ class CaselessStrEnum(Enum): return self._info(as_rst=True) -class FuzzyEnum(Enum): +class FuzzyEnum(Enum[G, S]): """An case-ignoring enum matching choices by unique prefixes/substrings.""" case_sensitive = False @@ -2730,12 +3160,12 @@ class FuzzyEnum(Enum): substring_matching = False def __init__( - self, - values, - default_value=Undefined, - case_sensitive=False, - substring_matching=False, - **kwargs, + self: FuzzyEnum[t.Any, t.Any], + values: t.Any, + default_value: t.Any = Undefined, + case_sensitive: bool = False, + substring_matching: bool = False, + **kwargs: t.Any, ): self.case_sensitive = case_sensitive self.substring_matching = substring_matching @@ -2747,11 +3177,7 @@ class FuzzyEnum(Enum): conv_func = (lambda c: c) if self.case_sensitive else lambda c: c.lower() substring_matching = self.substring_matching - match_func = ( - (lambda v, c: v in c) - if substring_matching - else (lambda v, c: c.startswith(v)) # type:ignore[no-any-return] - ) + match_func = (lambda v, c: v in c) if substring_matching else (lambda v, c: c.startswith(v)) value = conv_func(value) choices = self.values matches = [match_func(value, conv_func(c)) for c in choices] @@ -2776,18 +3202,58 @@ class FuzzyEnum(Enum): return self._info(as_rst=True) -class Container(Instance): +class Container(Instance[T]): """An instance of a container (list, set, etc.) To be subclassed by overriding klass. """ - klass: t.Optional[t.Union[str, t.Type[t.Any]]] = None + klass: type[T] | None = None _cast_types: t.Any = () _valid_defaults = SequenceTypes _trait = None _literal_from_string_pairs: t.Any = ("[]", "()") + @t.overload + def __init__( + self: Container[T], + kind: type[T], + *, + allow_none: Literal[False], + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: Container[T | None], + kind: type[T], + *, + allow_none: Literal[True], + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any | None = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: Container[T], + kind: type[T], + *, + help: str = ..., + read_only: bool = ..., + config: t.Any = ..., + trait: t.Any = ..., + default_value: t.Any = ..., + **kwargs: t.Any, + ): + ... + def __init__(self, trait=None, default_value=Undefined, **kwargs): """Create a container trait type from a list, set, or tuple. @@ -2863,7 +3329,7 @@ class Container(Instance): def validate(self, obj, value): if isinstance(value, self._cast_types): assert self.klass is not None - value = self.klass(value) # type:ignore[operator] + value = self.klass(value) # type:ignore[call-arg] value = super().validate(obj, value) if value is None: return value @@ -2884,7 +3350,7 @@ class Container(Instance): else: validated.append(v) assert self.klass is not None - return self.klass(validated) # type:ignore[operator] + return self.klass(validated) # type:ignore[call-arg] def class_init(self, cls, name): if isinstance(self._trait, TraitType): @@ -2932,19 +3398,21 @@ class Container(Instance): "You can pass `--{0} item` ... multiple times to add items to a list.".format( clsname + self.name, r ), - FutureWarning, + DeprecationWarning, + stacklevel=2, ) - return self.klass(literal_eval(r)) # type:ignore[operator] + return self.klass(literal_eval(r)) # type:ignore[call-arg] sig = inspect.signature(self.item_from_string) if "index" in sig.parameters: item_from_string = self.item_from_string else: # backward-compat: allow item_from_string to ignore index arg - item_from_string = lambda s, index=None: self.item_from_string(s) # noqa[E371] + def item_from_string(s, index=None): + return self.item_from_string(s) - return self.klass( + return self.klass( # type:ignore[call-arg] [item_from_string(s, index=idx) for idx, s in enumerate(s_list)] - ) # type:ignore[operator] + ) def item_from_string(self, s, index=None): """Cast a single item from a string @@ -2957,7 +3425,7 @@ class Container(Instance): return s -class List(Container): +class List(Container[t.List[t.Any]]): """An instance of a Python list.""" klass = list @@ -3075,7 +3543,7 @@ class Set(List): return "{" + list_repr[1:-1] + "}" -class Tuple(Container): +class Tuple(Container[t.Tuple[t.Any, ...]]): """An instance of a Python tuple.""" klass = tuple @@ -3204,7 +3672,7 @@ class Tuple(Container): # to opt out of instance_init -class Dict(Instance): +class Dict(Instance[t.Dict[t.Any, t.Any]]): """An instance of a Python dict. One or more traits can be passed to the constructor @@ -3352,8 +3820,7 @@ class Dict(Instance): def element_error(self, obj, element, validator, side="Values"): e = ( side - + " of the '%s' trait of %s instance must be %s, but a value of %s was specified." - % (self.name, class_of(obj), validator.info(), repr_type(element)) + + f" of the '{self.name}' trait of {class_of(obj)} instance must be {validator.info()}, but a value of {repr_type(element)} was specified." ) raise TraitError(e) @@ -3413,7 +3880,7 @@ class Dict(Instance): def from_string(self, s): """Load value from a single string""" if not isinstance(s, str): - raise TypeError(f"from_string expects a string, got {repr(s)} of type {type(s)}") + raise TypeError(f"from_string expects a string, got {s!r} of type {type(s)}") try: return self.from_string_list([s]) except Exception: @@ -3435,12 +3902,10 @@ class Dict(Instance): return None if len(s_list) == 1 and s_list[0].startswith("{") and s_list[0].endswith("}"): warn( - "--{0}={1} for dict-traits is deprecated in traitlets 5.0. " - "You can pass --{0} <key=value> ... multiple times to add items to a dict.".format( - self.name, - s_list[0], - ), - FutureWarning, + f"--{self.name}={s_list[0]} for dict-traits is deprecated in traitlets 5.0. " + f"You can pass --{self.name} <key=value> ... multiple times to add items to a dict.", + DeprecationWarning, + stacklevel=2, ) return literal_eval(s_list[0]) @@ -3463,11 +3928,7 @@ class Dict(Instance): if "=" not in s: raise TraitError( - "'%s' options must have the form 'key=value', got %s" - % ( - self.__class__.__name__, - repr(s), - ) + f"'{self.__class__.__name__}' options must have the form 'key=value', got {s!r}" ) key, value = s.split("=", 1) @@ -3482,7 +3943,7 @@ class Dict(Instance): return {key: value} -class TCPAddress(TraitType): +class TCPAddress(TraitType[G, S]): """A trait for an (ip, port) tuple. This allows for both IPv4 IP addresses as well as hostnames. @@ -3491,6 +3952,44 @@ class TCPAddress(TraitType): default_value = ("127.0.0.1", 0) info_text = "an (ip, port) tuple" + if t.TYPE_CHECKING: + + @t.overload + def __init__( + self: TCPAddress[tuple[str, int], tuple[str, int]], + default_value: bool | Sentinel = ..., + allow_none: Literal[False] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any = ..., + **kwargs: t.Any, + ): + ... + + @t.overload + def __init__( + self: TCPAddress[tuple[str, int] | None, tuple[str, int] | None], + default_value: bool | None | Sentinel = ..., + allow_none: Literal[True] = ..., + read_only: bool | None = ..., + help: str | None = ..., + config: t.Any = ..., + **kwargs: t.Any, + ): + ... + + def __init__( + self: TCPAddress[tuple[str, int] | None, tuple[str, int] | None] + | TCPAddress[tuple[str, int], tuple[str, int]], + default_value: bool | None | Sentinel = Undefined, + allow_none: Literal[True, False] = False, + read_only: bool | None = None, + help: str | None = None, + config: t.Any = None, + **kwargs: t.Any, + ): + ... + def validate(self, obj, value): if isinstance(value, tuple): if len(value) == 2: @@ -3510,7 +4009,7 @@ class TCPAddress(TraitType): return (ip, port) -class CRegExp(TraitType): +class CRegExp(TraitType["re.Pattern[t.Any]", t.Union["re.Pattern[t.Any]", str]]): """A casting compiled regular expression trait. Accepts both strings and compiled regular expressions. The resulting @@ -3525,7 +4024,7 @@ class CRegExp(TraitType): self.error(obj, value) -class UseEnum(TraitType): +class UseEnum(TraitType[t.Any, t.Any]): """Use a Enum class as model for the data type description. Note that if no default-value is provided, the first enum-value is used as default-value. @@ -3552,14 +4051,14 @@ class UseEnum(TraitType): assert entity.color is Color.green """ - default_value: t.Optional[enum.Enum] = None + default_value: enum.Enum | None = None info_text = "Trait type adapter to a Enum class" def __init__(self, enum_class, default_value=None, **kwargs): assert issubclass(enum_class, enum.Enum), "REQUIRE: enum.Enum, but was: %r" % enum_class allow_none = kwargs.get("allow_none", False) if default_value is None and not allow_none: - default_value = list(enum_class.__members__.values())[0] + default_value = next(iter(enum_class.__members__.values())) super().__init__(default_value=default_value, **kwargs) self.enum_class = enum_class self.name_prefix = enum_class.__name__ + "." @@ -3622,7 +4121,7 @@ class UseEnum(TraitType): return self._info(as_rst=True) -class Callable(TraitType): +class Callable(TraitType[t.Callable[..., t.Any], t.Callable[..., t.Any]]): """A trait which is callable. Notes diff --git a/contrib/python/traitlets/py3/traitlets/utils/warnings.py b/contrib/python/traitlets/py3/traitlets/utils/warnings.py new file mode 100644 index 0000000000..216b23dc2b --- /dev/null +++ b/contrib/python/traitlets/py3/traitlets/utils/warnings.py @@ -0,0 +1,61 @@ +import inspect +import os +import warnings + + +def warn(msg, category, *, stacklevel, source=None): + """Like warnings.warn(), but category and stacklevel are required. + + You pretty much never want the default stacklevel of 1, so this helps + encourage setting it explicitly.""" + return warnings.warn(msg, category=category, stacklevel=stacklevel, source=source) + + +def deprecated_method(method, cls, method_name, msg): + """Show deprecation warning about a magic method definition. + + Uses warn_explicit to bind warning to method definition instead of triggering code, + which isn't relevant. + """ + warn_msg = f"{cls.__name__}.{method_name} is deprecated in traitlets 4.1: {msg}" + + for parent in inspect.getmro(cls): + if method_name in parent.__dict__: + cls = parent + break + # limit deprecation messages to once per package + package_name = cls.__module__.split(".", 1)[0] + key = (package_name, msg) + if not should_warn(key): + return + try: + fname = inspect.getsourcefile(method) or "<unknown>" + lineno = inspect.getsourcelines(method)[1] or 0 + except (OSError, TypeError) as e: + # Failed to inspect for some reason + warn( + warn_msg + ("\n(inspection failed) %s" % e), + DeprecationWarning, + stacklevel=2, + ) + else: + warnings.warn_explicit(warn_msg, DeprecationWarning, fname, lineno) + + +_deprecations_shown = set() + + +def should_warn(key): + """Add our own checks for too many deprecation warnings. + + Limit to once per package. + """ + env_flag = os.environ.get("TRAITLETS_ALL_DEPRECATIONS") + if env_flag and env_flag != "0": + return True + + if key not in _deprecations_shown: + _deprecations_shown.add(key) + return True + else: + return False diff --git a/contrib/python/traitlets/py3/ya.make b/contrib/python/traitlets/py3/ya.make index fc5ec80cbc..127b644a52 100644 --- a/contrib/python/traitlets/py3/ya.make +++ b/contrib/python/traitlets/py3/ya.make @@ -4,7 +4,7 @@ PY3_LIBRARY() PROVIDES(python_traitlets) -VERSION(5.9.0) +VERSION(5.10.0) LICENSE(BSD-3-Clause) @@ -35,6 +35,7 @@ PY_SRCS( traitlets/utils/nested_update.py traitlets/utils/sentinel.py traitlets/utils/text.py + traitlets/utils/warnings.py ) RESOURCE_FILES( |