From f3e2722fe05d7071e85239539fde4a1f3165f614 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Tue, 6 Dec 2022 19:25:07 -0600 Subject: [PATCH 1/2] adopt ruff and address lint --- .github/dependabot.yml | 6 +- .github/workflows/tests.yml | 23 +++-- .pre-commit-config.yaml | 81 ++++----------- docs/source/conf.py | 2 +- docs/sphinxext/github.py | 10 +- pyproject.toml | 106 ++++++++++++++++---- traitlets/config/application.py | 2 +- traitlets/config/configurable.py | 1 + traitlets/config/loader.py | 19 ++-- traitlets/config/tests/test_application.py | 17 ++-- traitlets/config/tests/test_configurable.py | 6 +- traitlets/config/tests/test_loader.py | 24 ++--- traitlets/tests/test_traitlets.py | 78 +++++++------- traitlets/tests/test_traitlets_enum.py | 7 +- traitlets/tests/utils.py | 4 +- traitlets/traitlets.py | 20 ++-- traitlets/utils/bunch.py | 4 +- traitlets/utils/getargspec.py | 1 + traitlets/utils/importstring.py | 4 +- 19 files changed, 224 insertions(+), 191 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f10a5bc1..cbd920f6 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,8 +1,10 @@ version: 2 updates: - # Set update schedule for GitHub Actions - package-ecosystem: "github-actions" directory: "/" schedule: - # Check for updates to GitHub Actions every weekday + interval: "weekly" + - package-ecosystem: "pip" + directory: "/" + schedule: interval: "weekly" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8c8280e8..a8dfc3ed 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -57,6 +57,19 @@ jobs: run: | hatch run test:nowarn || hatch run test:nowarn --lf + test_lint: + name: Test Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Run Linters + run: | + hatch run typing:test + hatch run lint:style + pipx run 'validate-pyproject[all]' pyproject.toml + pipx run doc8 --max-line-length=200 + test_docs: timeout-minutes: 10 runs-on: ubuntu-latest @@ -121,19 +134,11 @@ jobs: with: token: ${{ secrets.GITHUB_TOKEN }} - pre_commit: - name: pre-commit - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - uses: jupyterlab/maintainer-tools/.github/actions/pre-commit@v1 - tests_check: # This job does nothing and is only used for the branch protection if: always() needs: - tests - - pre_commit + - test_lint - test_docs - test_minimum_versions - test_prereleases diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fbb1ade1..ff708b29 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,85 +1,40 @@ +ci: + autoupdate_schedule: monthly + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - - id: debug-statements - stages: [manual] + - id: end-of-file-fixer + - id: check-case-conflict + - id: check-executables-have-shebangs + - id: requirements-txt-fixer - id: check-added-large-files - stages: [manual] - id: check-case-conflict - stages: [manual] - id: check-toml - stages: [manual] - id: check-yaml - stages: [manual] + - id: debug-statements - id: forbid-new-submodules - stages: [manual] - id: check-builtin-literals - stages: [manual] - - id: check-case-conflict - stages: [manual] - - id: check-executables-have-shebangs - stages: [manual] - - id: end-of-file-fixer - - id: requirements-txt-fixer - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 22.10.0 - hooks: - - id: black - args: ["--line-length", "100"] - - - repo: https://github.com/PyCQA/isort - rev: 5.10.1 - hooks: - - id: isort - files: \.py$ - args: [--profile=black] - - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.991 - hooks: - - id: mypy - args: ["--config-file", "pyproject.toml"] - exclude: "traitlets/.*tests/.*.py" - additional_dependencies: [pytest, types-docutils, sphinx] - stages: [manual] - - - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.10.1 + - repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.19.2 hooks: - - id: validate-pyproject - stages: [manual] + - id: check-github-workflows - repo: https://github.com/executablebooks/mdformat rev: 0.7.16 hooks: - id: mdformat - - repo: https://github.com/asottile/pyupgrade - rev: v3.3.0 - hooks: - - id: pyupgrade - args: [--py37-plus] - - - repo: https://github.com/PyCQA/doc8 - rev: v1.0.0 - hooks: - - id: doc8 - args: [--max-line-length=200] - stages: [manual] - - - repo: https://github.com/john-hen/Flake8-pyproject - rev: 1.2.2 + - repo: https://github.com/psf/black + rev: 22.10.0 hooks: - - id: Flake8-pyproject - alias: flake8 - additional_dependencies: - ["flake8-bugbear==22.6.22", "flake8-implicit-str-concat==0.2.0"] - stages: [manual] + - id: black - - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.19.2 + - repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.0.165 hooks: - - id: check-github-workflows + - id: ruff + args: ["--fix"] diff --git a/docs/source/conf.py b/docs/source/conf.py index 1f74bd82..a332407b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,7 +29,7 @@ # We load the ipython release info into a dict by explicit execution _release = {} # type:ignore -exec( +exec( # noqa compile( open(osp.join(ROOT, "traitlets/_version.py")).read(), "../../traitlets/_version.py", diff --git a/docs/sphinxext/github.py b/docs/sphinxext/github.py index ca73224d..fabbab53 100644 --- a/docs/sphinxext/github.py +++ b/docs/sphinxext/github.py @@ -19,7 +19,7 @@ from docutils import nodes, utils from docutils.parsers.rst.roles import set_classes -from sphinx.util.logging import getLogger +from sphinx.util.logging import getLogger # type:ignore info = getLogger(__name__).info @@ -41,7 +41,9 @@ def make_link_node(rawtext, app, type, slug, options): if not base.endswith("/"): base += "/" except AttributeError as err: - raise ValueError("github_project_url configuration value is not set (%s)" % str(err)) + raise ValueError( + "github_project_url configuration value is not set (%s)" % str(err) + ) from err ref = base + type + "/" + slug + "/" set_classes(options) @@ -147,7 +149,9 @@ def ghcommit_role(name, rawtext, text, lineno, inliner, options=None, content=No if not base.endswith("/"): base += "/" except AttributeError as err: - raise ValueError("github_project_url configuration value is not set (%s)" % str(err)) + raise ValueError( + "github_project_url configuration value is not set (%s)" % str(err) + ) from err ref = base + text node = nodes.reference(rawtext, text[:6], refuri=ref, **options) diff --git a/pyproject.toml b/pyproject.toml index 6b510bcb..6afe1e49 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,8 @@ docs = [ "pydata-sphinx-theme", "sphinx" ] +lint = ["black>=22.6.0", "mdformat>0.7", "ruff>=0.0.156"] +typing = ["mypy>=0.990"] [tool.hatch.version] path = "traitlets/_version.py" @@ -50,6 +52,26 @@ dependencies = ["coverage", "pytest-cov"] test = "python -m pytest -vv --cov traitlets --cov-branch --cov-report term-missing:skip-covered {args}" nowarn = "test -W default {args}" +[tool.hatch.envs.typing] +features = ["test", "typing"] +dependencies = ["mypy>=0.990"] +[tool.hatch.envs.typing.scripts] +test = "mypy --install-types --non-interactive {args:.}" + +[tool.hatch.envs.lint] +features = ["lint"] +[tool.hatch.envs.lint.scripts] +style = [ + "ruff {args:.}", + "black --check --diff {args:.}", + "mdformat --check {args:docs *.md}" +] +fmt = [ + "black {args:.}", + "ruff --fix {args:.}", + "mdformat {args:docs *.md}" +] + [tool.mypy] check_untyped_defs = true disallow_any_generics = true @@ -115,27 +137,69 @@ exclude_lines = [ "@(abc\\.)?abstractmethod", ] -[tool.flake8] -ignore = "E501, W503, E402" -builtins = "c, get_config" -exclude = [ - ".cache", - ".github", - "docs", - "setup.py", +[tool.black] +line-length = 100 +skip-string-normalization = true +target-version = ["py37"] + +[tool.ruff] +target-version = "py37" +line-length = 100 +select = [ + "A", "B", "C", "E", "F", "FBT", "I", "N", "Q", "RUF", "S", "T", + "UP", "W", "YTT", ] -enable-extensions = "G" -extend-ignore = [ - "G001", "G002", "G004", "G200", "G201", "G202", - # black adds spaces around ':' - "E203", +ignore = [ + # Allow non-abstract empty methods in abstract base classes + "B027", + # Ignore McCabe complexity + "C901", + # Allow boolean positional values in function calls, like `dict.get(... True)` + "FBT003", + # Use of `assert` detected + "S101", + # Line too long + "E501", + # Relative imports are banned + "I252", + # Boolean ... in function definition + "FBT001", "FBT002", + # Module level import not at top of file + "E402", + # A001/A002/A003 .. is shadowing a python builtin + "A001", "A002", "A003", + # Possible hardcoded password + "S105", "S106", + # Q000 Single quotes found but double quotes preferred + "Q000", + # N806 Variable `B` in function should be lowercase + "N806", + # T201 `print` found + "T201", + # N802 Function name `CreateWellKnownSid` should be lowercase + "N802", "N803", + # C408 Unnecessary `dict` call (rewrite as a literal) + "C408", + # N801 Class name `directional_link` should use CapWords convention + "N801", ] -per-file-ignores = [ - # B011: Do not call assert False since python -O removes these calls - # F841 local variable 'foo' is assigned to but never used - "traitlets/tests/*: B011", "F841", - # F401 'foo' imported but unused - # F403 'from foo import *' used; unable to detect undefined names - "traitlets/__init__.py: F401", "F403", - "traitlets/*/__init__.py: F401", "F403", +unfixable = [ + # Don't touch print statements + "T201", + # Don't touch noqa lines + "RUF100", ] + +[tool.ruff.per-file-ignores] +# B011 Do not call assert False since python -O removes these calls +# F841 local variable 'foo' is assigned to but never used +# C408 Unnecessary `dict` call +# E402 Module level import not at top of file +# T201 `print` found +# B007 Loop control variable `i` not used within the loop body. +# N802 Function name `assertIn` should be lowercase +# F841 Local variable `t` is assigned to but never used +"traitlets/tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802", "F841"] +# F401 `_version.__version__` imported but unused +# F403 `from .traitlets import *` used; unable to detect undefined names +"traitlets/*__init__.py" = ["F401", "F403"] diff --git a/traitlets/config/application.py b/traitlets/config/application.py index ec2b43e2..b7c77270 100644 --- a/traitlets/config/application.py +++ b/traitlets/config/application.py @@ -952,7 +952,7 @@ def generate_config_file(self, classes=None): """generate default config file from Configurables""" lines = ["# Configuration file for %s." % self.name] lines.append("") - lines.append("c = get_config() # noqa") + lines.append("c = get_config() #" + "noqa") lines.append("") classes = self.classes if classes is None else classes config_classes = list(self._classes_with_config_traits(classes)) diff --git a/traitlets/config/configurable.py b/traitlets/config/configurable.py index 5f23df9e..919103f8 100644 --- a/traitlets/config/configurable.py +++ b/traitlets/config/configurable.py @@ -24,6 +24,7 @@ from .loader import Config, DeferredConfig, LazyConfigValue, _is_section_key + # ----------------------------------------------------------------------------- # Helper classes for Configurables # ----------------------------------------------------------------------------- diff --git a/traitlets/config/loader.py b/traitlets/config/loader.py index 9c970b16..14c22b5f 100644 --- a/traitlets/config/loader.py +++ b/traitlets/config/loader.py @@ -16,6 +16,7 @@ from ..utils import cast_unicode, filefind + # ----------------------------------------------------------------------------- # Exceptions # ----------------------------------------------------------------------------- @@ -29,7 +30,7 @@ class ConfigLoaderError(ConfigError): pass -class ConfigFileNotFound(ConfigError): +class ConfigFileNotFound(ConfigError): # noqa pass @@ -77,7 +78,7 @@ def print_help(self, file=None): def execfile(fname, glob): with open(fname, "rb") as f: - exec(compile(f.read(), fname, "exec"), glob, glob) + exec(compile(f.read(), fname, "exec"), glob, glob) # noqa class LazyConfigValue(HasTraits): @@ -358,7 +359,7 @@ def __getattr__(self, key): try: return self.__getitem__(key) except KeyError as e: - raise AttributeError(e) + raise AttributeError(e) from e def __setattr__(self, key, value): if key.startswith("__"): @@ -366,7 +367,7 @@ def __setattr__(self, key, value): try: self.__setitem__(key, value) except KeyError as e: - raise AttributeError(e) + raise AttributeError(e) from e def __delattr__(self, key): if key.startswith("__"): @@ -374,7 +375,7 @@ def __delattr__(self, key): try: dict.__delitem__(self, key) except KeyError as e: - raise AttributeError(e) + raise AttributeError(e) from e class DeferredConfig: @@ -571,7 +572,7 @@ def load_config(self): try: self._find_file() except OSError as e: - raise ConfigFileNotFound(str(e)) + raise ConfigFileNotFound(str(e)) from e dct = self._read_file_as_dict() self.config = self._convert_to_config(dct) return self.config @@ -621,7 +622,7 @@ def load_config(self): try: self._find_file() except OSError as e: - raise ConfigFileNotFound(str(e)) + raise ConfigFileNotFound(str(e)) from e self._read_file_as_dict() return self.config @@ -655,7 +656,7 @@ def get_config(): ) conf_filename = self.full_filename with open(conf_filename, "rb") as f: - exec(compile(f.read(), conf_filename, "exec"), namespace, namespace) + exec(compile(f.read(), conf_filename, "exec"), namespace, namespace) # noqa class CommandLineConfigLoader(ConfigLoader): @@ -1069,7 +1070,7 @@ def _convert_to_config(self): # DeferredList->list, etc if isinstance(rhs, DeferredConfig): rhs = rhs._super_repr() - raise ArgumentError(f"Error loading argument {lhs}={rhs}, {e}") + raise ArgumentError(f"Error loading argument {lhs}={rhs}, {e}") from e for subc in self.parsed_data._flags: self._load_flag(subc) diff --git a/traitlets/config/tests/test_application.py b/traitlets/config/tests/test_application.py index 57d71ba6..70e4a08f 100644 --- a/traitlets/config/tests/test_application.py +++ b/traitlets/config/tests/test_application.py @@ -11,6 +11,7 @@ import logging import os import sys +import typing as t from io import StringIO from tempfile import TemporaryDirectory from unittest import TestCase @@ -68,7 +69,7 @@ class MyApp(Application): name = Unicode("myapp") running = Bool(False, help="Is the app running?").tag(config=True) - classes = List([Bar, Foo]) + classes = List([Bar, Foo]) # type:ignore config_file = Unicode("", help="Load this config file").tag(config=True) warn_tpyo = Unicode( @@ -77,7 +78,7 @@ class MyApp(Application): help="Should print a warning if `MyApp.warn-typo=...` command is passed", ) - aliases = {} + aliases: t.Dict[t.Any, t.Any] = {} aliases.update(Application.aliases) aliases.update( { @@ -94,7 +95,7 @@ class MyApp(Application): } ) - flags = {} + flags: t.Dict[t.Any, t.Any] = {} flags.update(Application.flags) flags.update( { @@ -137,7 +138,7 @@ def test_basic(self): app = MyApp() self.assertEqual(app.name, "myapp") self.assertEqual(app.running, False) - self.assertEqual(app.classes, [MyApp, Bar, Foo]) + self.assertEqual(app.classes, [MyApp, Bar, Foo]) # type:ignore self.assertEqual(app.config_file, "") def test_mro_discovery(self): @@ -375,7 +376,7 @@ def test_aliases_multiple(self): # Test multiple > 2 aliases for the same argument class TestMultiAliasApp(Application): foo = Integer(config=True) - aliases = {("f", "bar", "qux"): "TestMultiAliasApp.foo"} + aliases = {("f", "bar", "qux"): "TestMultiAliasApp.foo"} # type:ignore app = TestMultiAliasApp() app.parse_command_line(["-f", "3"]) @@ -519,7 +520,7 @@ class NoTraits(Foo, Bar, NotInConfig): pass app = MyApp() - app.classes.append(NoTraits) + app.classes.append(NoTraits) # type:ignore conf_txt = app.generate_config_file() print(conf_txt) @@ -663,7 +664,7 @@ def test_loaded_config_files(self): # Attempt to update, ensure error... with self.assertRaises(AttributeError): - app.loaded_config_files = "/foo" + app.loaded_config_files = "/foo" # type:ignore # ensure it can't be udpated via append app.loaded_config_files.append("/bar") @@ -707,7 +708,7 @@ class Sub2(Application): class Sub1(Application): - subcommands = { + subcommands: dict = { # type:ignore "sub2": (Sub2, "Application class"), "sub3": (lambda root: Sub3(parent=root, flag=True), "factory"), } diff --git a/traitlets/config/tests/test_configurable.py b/traitlets/config/tests/test_configurable.py index e8163d3f..5edd7db5 100644 --- a/traitlets/config/tests/test_configurable.py +++ b/traitlets/config/tests/test_configurable.py @@ -9,11 +9,7 @@ from pytest import mark from traitlets.config.application import Application -from traitlets.config.configurable import ( - Configurable, - LoggingConfigurable, - SingletonConfigurable, -) +from traitlets.config.configurable import Configurable, LoggingConfigurable, SingletonConfigurable from traitlets.config.loader import Config from traitlets.log import get_logger from traitlets.traitlets import ( diff --git a/traitlets/config/tests/test_loader.py b/traitlets/config/tests/test_loader.py index c26e6991..7355544c 100644 --- a/traitlets/config/tests/test_loader.py +++ b/traitlets/config/tests/test_loader.py @@ -366,24 +366,24 @@ class CSub(CBase): class TestArgParseKVCL(TestKeyValueCL): - klass = KVArgParseConfigLoader + klass = KVArgParseConfigLoader # type:ignore def test_no_cast_literals(self): - cl = self.klass(log=log) + cl = self.klass(log=log) # type:ignore # test ipython -c 1 doesn't cast to int argv = ["-c", "1"] config = cl.load_config(argv, aliases=dict(c="IPython.command_to_run")) assert config.IPython.command_to_run == "1" def test_int_literals(self): - cl = self.klass(log=log) + cl = self.klass(log=log) # type:ignore # test ipython -c 1 doesn't cast to int argv = ["-c", "1"] config = cl.load_config(argv, aliases=dict(c="IPython.command_to_run")) assert config.IPython.command_to_run == "1" def test_unicode_alias(self): - cl = self.klass(log=log) + cl = self.klass(log=log) # type:ignore argv = ["--a=épsîlön"] config = cl.load_config(argv, aliases=dict(a="A.a")) print(dict(config)) @@ -392,7 +392,7 @@ def test_unicode_alias(self): self.assertEqual(config.A.a, "épsîlön") def test_expanduser2(self): - cl = self.klass(log=log) + cl = self.klass(log=log) # type:ignore argv = ["-a", "~/1/2/3", "--b", "'~/1/2/3'"] config = cl.load_config(argv, aliases=dict(a="A.a", b="A.b")) @@ -405,13 +405,13 @@ class A(Configurable): self.assertEqual(a.b, "~/1/2/3") def test_eval(self): - cl = self.klass(log=log) + cl = self.klass(log=log) # type:ignore argv = ["-c", "a=5"] config = cl.load_config(argv, aliases=dict(c="A.c")) self.assertEqual(config.A.c, "a=5") def test_seq_traits(self): - cl = self.klass(log=log, classes=(CBase, CSub)) + cl = self.klass(log=log, classes=(CBase, CSub)) # type:ignore aliases = {"a3": "CBase.c", "a5": "CSub.e"} argv = ( "--CBase.a A --CBase.a 2 --CBase.b 1 --CBase.b 3 --a3 AA --CBase.c BB " @@ -427,14 +427,14 @@ def test_seq_traits(self): assert config.CSub.e == ("1", "bcd") def test_seq_traits_single_empty_string(self): - cl = self.klass(log=log, classes=(CBase,)) + cl = self.klass(log=log, classes=(CBase,)) # type:ignore aliases = {"seqopt": "CBase.c"} argv = ["--seqopt", ""] config = cl.load_config(argv, aliases=aliases) self.assertEqual(config.CBase.c, [""]) def test_dict_traits(self): - cl = self.klass(log=log, classes=(CBase, CSub)) + cl = self.klass(log=log, classes=(CBase, CSub)) # type:ignore aliases = {"D": "CBase.adict", "E": "CSub.bdict"} argv = ["-D", "k1=v1", "-D=k2=2", "-D", "k3=v 3", "-E", "k=v", "-E", "22=222"] config = cl.load_config(argv, aliases=aliases) @@ -444,13 +444,13 @@ def test_dict_traits(self): def test_mixed_seq_positional(self): aliases = {"c": "Class.trait"} - cl = self.klass(log=log, aliases=aliases) + cl = self.klass(log=log, aliases=aliases) # type:ignore assignments = [("-c", "1"), ("--Class.trait=2",), ("--c=3",), ("--Class.trait", "4")] positionals = ["a", "b", "c"] # test with positionals at any index for idx in range(len(assignments) + 1): argv_parts = assignments[:] - argv_parts[idx:idx] = (positionals,) + argv_parts[idx:idx] = (positionals,) # type:ignore argv = list(chain(*argv_parts)) config = cl.load_config(argv) @@ -459,7 +459,7 @@ def test_mixed_seq_positional(self): def test_split_positional(self): """Splitting positionals across flags is no longer allowed in traitlets 5""" - cl = self.klass(log=log) + cl = self.klass(log=log) # type:ignore argv = ["a", "--Class.trait=5", "b"] with pytest.raises(SystemExit): cl.load_config(argv) diff --git a/traitlets/tests/test_traitlets.py b/traitlets/tests/test_traitlets.py index ab01d024..6021b7e8 100644 --- a/traitlets/tests/test_traitlets.py +++ b/traitlets/tests/test_traitlets.py @@ -8,6 +8,7 @@ import pickle import re +import typing as t from unittest import TestCase import pytest @@ -97,14 +98,14 @@ class A(HasTraits): a = TraitType a = A() - assert a.a is Undefined + assert a.a is Undefined # type:ignore def test_set(self): class A(HasTraitsStub): a = TraitType a = A() - a.a = 10 + a.a = 10 # type:ignore self.assertEqual(a.a, 10) self.assertEqual(a._notify_name, "a") self.assertEqual(a._notify_old, Undefined) @@ -119,7 +120,7 @@ class A(HasTraitsStub): tt = MyTT a = A() - a.tt = 10 + a.tt = 10 # type:ignore self.assertEqual(a.tt, -1) def test_default_validate(self): @@ -146,7 +147,7 @@ class A(HasTraits): tt = TraitType a = A() - self.assertEqual(A.tt.info(), "any value") + self.assertEqual(A.tt.info(), "any value") # type:ignore def test_error(self): class A(HasTraits): @@ -226,7 +227,7 @@ def _x_validate(self, value, _): def _x_changed(self): pass - obj = ShouldWarn() + obj = ShouldWarn() # type:ignore obj.x = 5 assert obj.x == 5 @@ -535,16 +536,16 @@ def callback0(): self.cb = () def callback1(name): - self.cb = (name,) + self.cb = (name,) # type:ignore def callback2(name, new): - self.cb = (name, new) + self.cb = (name, new) # type:ignore def callback3(name, old, new): - self.cb = (name, old, new) + self.cb = (name, old, new) # type:ignore def callback4(name, old, new, obj): - self.cb = (name, old, new, obj) + self.cb = (name, old, new, obj) # type:ignore class A(HasTraits): a = Int() @@ -729,7 +730,7 @@ def _any_changed(self, change): self.assertTrue(change in a._notify_any) class B(A): - b = Float() + b = Float() # type:ignore _notify2 = [] @observe("b") @@ -738,7 +739,7 @@ def _b_changed(self, change): b = B() b.a = 10 - b.b = 10.0 + b.b = 10.0 # type:ignore change = change_dict("a", 0, 10, b, "change") self.assertTrue(change in b._notify1) change = change_dict("b", 0.0, 10.0, b, "change") @@ -1019,7 +1020,7 @@ class A(HasTraits): self.assertRaises(ImportError, A) - class A(HasTraits): + class A(HasTraits): # type:ignore klass = Type("rub.adub.Duck") self.assertRaises(ImportError, A) @@ -1042,7 +1043,7 @@ def test_str_klass(self): class A(HasTraits): klass = Type("traitlets.config.Config") - from traitlets.config import Config + from traitlets.config import Config # type:ignore a = A() a.klass = Config @@ -1055,7 +1056,7 @@ class A(HasTraits): klass = Type() a = A(klass="traitlets.config.Config") - from traitlets.config import Config + from traitlets.config import Config # type:ignore self.assertEqual(a.klass, Config) @@ -1237,7 +1238,7 @@ class TraitTestBase(TestCase): """A best testing class for basic trait types.""" def assign(self, value): - self.obj.value = value + self.obj.value = value # type:ignore def coerce(self, value): return value @@ -1246,7 +1247,7 @@ def test_good_values(self): if hasattr(self, "_good_values"): for value in self._good_values: self.assign(value) - self.assertEqual(self.obj.value, self.coerce(value)) + self.assertEqual(self.obj.value, self.coerce(value)) # type:ignore def test_bad_values(self): if hasattr(self, "_bad_values"): @@ -1258,7 +1259,7 @@ def test_bad_values(self): def test_default_value(self): if hasattr(self, "_default_value"): - self.assertEqual(self._default_value, self.obj.value) + self.assertEqual(self._default_value, self.obj.value) # type:ignore def test_allow_none(self): if ( @@ -1266,13 +1267,13 @@ def test_allow_none(self): and hasattr(self, "_good_values") and None in self._bad_values ): - trait = self.obj.traits()["value"] + trait = self.obj.traits()["value"] # type:ignore try: trait.allow_none = True self._bad_values.remove(None) # skip coerce. Allow None casts None to None. self.assign(None) - self.assertEqual(self.obj.value, None) + self.assertEqual(self.obj.value, None) # type:ignore self.test_good_values() self.test_bad_values() finally: @@ -1283,7 +1284,7 @@ def test_allow_none(self): def tearDown(self): # restore default value after tests, if set if hasattr(self, "_default_value"): - self.obj.value = self._default_value + self.obj.value = self._default_value # type:ignore class AnyTrait(HasTraits): @@ -1297,7 +1298,7 @@ class AnyTraitTest(TraitTestBase): _default_value = None _good_values = [10.0, "ten", [10], {"ten": 10}, (10,), None, 1j] - _bad_values = [] + _bad_values: t.Any = [] class UnionTrait(HasTraits): @@ -1385,7 +1386,7 @@ class MinBoundCIntTrait(HasTraits): class TestMinBoundCInt(TestCInt): - obj = MinBoundCIntTrait() + obj = MinBoundCIntTrait() # type:ignore _default_value = 5 _good_values = [3, 3.0, "3"] @@ -1465,7 +1466,7 @@ class MaxBoundCLongTrait(HasTraits): class TestMaxBoundCLong(TestCLong): - obj = MaxBoundCLongTrait() + obj = MaxBoundCLongTrait() # type:ignore _default_value = 5 _good_values = [10, "10", 10.3] @@ -1477,7 +1478,7 @@ class IntegerTrait(HasTraits): class TestInteger(TestLong): - obj = IntegerTrait() + obj = IntegerTrait() # type:ignore _default_value = 1 def coerce(self, n): @@ -1672,7 +1673,7 @@ class TestList(TraitTestBase): obj = ListTrait() - _default_value = [] + _default_value: t.List[t.Any] = [] _good_values = [[], [1], list(range(10)), (1, 2)] _bad_values = [10, [1, "a"], "a"] @@ -1695,7 +1696,7 @@ class TestNoneInstanceList(TraitTestBase): obj = NoneInstanceListTrait() - _default_value = [] + _default_value: t.List[t.Any] = [] _good_values = [[Foo(), Foo()], []] _bad_values = [[None], [Foo(), None]] @@ -1713,7 +1714,7 @@ def test_klass(self): """Test that the instance klass is properly assigned.""" self.assertIs(self.obj.traits()["value"]._trait.klass, Foo) - _default_value = [] + _default_value: t.List[t.Any] = [] _good_values = [[Foo(), Foo()], []] _bad_values = [ [ @@ -1735,7 +1736,7 @@ class TestUnionListTrait(TraitTestBase): obj = UnionListTrait() - _default_value = [] + _default_value: t.List[t.Any] = [] _good_values = [[True, 1], [False, True]] _bad_values = [[1, "True"], False] @@ -1866,7 +1867,7 @@ class C(HasTraits): def test_subclass_default_value(Trait, default_value): """Test deprecated default_value=None behavior for Container subclass traits""" - class SubclassTrait(Trait): + class SubclassTrait(Trait): # type:ignore def __init__(self, default_value=None): super().__init__(default_value=default_value) @@ -1900,7 +1901,7 @@ class DictTrait(HasTraits): def test_dict_assignment(): - d = {} + d: t.Dict[str, int] = {} c = DictTrait() c.value = d d["a"] = 5 @@ -2270,7 +2271,7 @@ class B(HasTraits): a.value = 5 self.assertEqual(b.count, 5) # Change one the value of the target and check that it has no impact on the source - b.value = 6 + b.value = 6 # type:ignore self.assertEqual(a.value, 5) def test_unlink_link(self): @@ -2522,7 +2523,7 @@ def test_klass(self): """Test that the instance klass is properly assigned.""" self.assertIs(self.obj.traits()["value"]._trait.klass, ForwardDeclaredBar) - _default_value = [] + _default_value: t.List[t.Any] = [] _good_values = [ [ForwardDeclaredBar(), ForwardDeclaredBarSub()], [], @@ -2546,7 +2547,7 @@ def test_klass(self): """Test that the instance klass is properly assigned.""" self.assertIs(self.obj.traits()["value"]._trait.klass, ForwardDeclaredBar) - _default_value = [] + _default_value: t.List[t.Any] = [] _good_values = [ [ForwardDeclaredBar, ForwardDeclaredBarSub], [], @@ -2574,6 +2575,7 @@ def setUp(self): def notify1(self, name, old, new): self._notify1.append((name, old, new)) + @t.no_type_check def test_notify_all(self): class A(HasTraits): pass @@ -2859,7 +2861,7 @@ class BA(B, A): def test_cls_self_argument(): class X(HasTraits): - def __init__(__self, cls, self): + def __init__(__self, cls, self): # noqa pass x = X(cls=None, self=None) @@ -2872,7 +2874,7 @@ class C(HasTraits): def _a_default(self): return "default method" - C._a_default = lambda self: "overridden" + C._a_default = lambda self: "overridden" # type:ignore c = C() assert c.a == "overridden" @@ -2885,7 +2887,7 @@ class C(HasTraits): def _a_default(self): return "default method" - C._a_default = lambda self: "overridden" + C._a_default = lambda self: "overridden" # type:ignore c = C() assert c.a == "overridden" @@ -2925,13 +2927,13 @@ def _from_string_test(traittype, s, expected): else: trait = traittype(allow_none=True) if isinstance(s, list): - cast = trait.from_string_list + cast = trait.from_string_list # type:ignore else: cast = trait.from_string if type(expected) is type and issubclass(expected, Exception): with pytest.raises(expected): value = cast(s) - trait.validate(CrossValidationStub(), value) + trait.validate(CrossValidationStub(), value) # type:ignore else: value = cast(s) assert value == expected diff --git a/traitlets/tests/test_traitlets_enum.py b/traitlets/tests/test_traitlets_enum.py index 892a8451..52b7914e 100644 --- a/traitlets/tests/test_traitlets_enum.py +++ b/traitlets/tests/test_traitlets_enum.py @@ -8,6 +8,7 @@ from traitlets import CaselessStrEnum, Enum, FuzzyEnum, HasTraits, TraitError, UseEnum + # ----------------------------------------------------------------------------- # TEST SUPPORT: # ----------------------------------------------------------------------------- @@ -51,7 +52,7 @@ def test_assign_enum_value(self): def test_assign_all_enum_values(self): # pylint: disable=no-member - enum_values = [value for value in Color.__members__.values()] + enum_values = list(Color.__members__.values()) for value in enum_values: self.assertIsInstance(value, Color) example = self.Example() @@ -80,7 +81,7 @@ def test_assign_enum_value_name(self): enum_value = Color.__members__.get(value) example.color = value self.assertIs(example.color, enum_value) - self.assertEqual(example.color.name, value) + self.assertEqual(example.color.name, value) # type:ignore def test_assign_scoped_enum_value_name(self): # -- CONVERT: string => Enum value (item) @@ -115,7 +116,7 @@ def test_assign_enum_value_number(self): example = self.Example() example.color = value self.assertIsInstance(example.color, Color) - self.assertEqual(example.color.value, value) + self.assertEqual(example.color.value, value) # type:ignore def test_assign_bad_enum_value_number__raises_error(self): # -- CONVERT: number => Enum value (item) diff --git a/traitlets/tests/utils.py b/traitlets/tests/utils.py index c14dc83e..79d85a63 100644 --- a/traitlets/tests/utils.py +++ b/traitlets/tests/utils.py @@ -6,8 +6,8 @@ def get_output_error_code(cmd): """Get stdout, stderr, and exit code from running a command""" p = Popen(cmd, stdout=PIPE, stderr=PIPE) out, err = p.communicate() - out = out.decode("utf8", "replace") - err = err.decode("utf8", "replace") + out = out.decode("utf8", "replace") # type:ignore + err = err.decode("utf8", "replace") # type:ignore return out, err, p.returncode diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py index 8c162567..264604ca 100644 --- a/traitlets/traitlets.py +++ b/traitlets/traitlets.py @@ -666,9 +666,9 @@ def get(self, obj, cls=None): ) ) return value - except Exception: + except Exception as e: # This should never be reached. - raise TraitError("Unexpected error in TraitType: default value not set properly") + raise TraitError("Unexpected error in TraitType: default value not set properly") from e else: return value @@ -934,7 +934,7 @@ class MetaHasDescriptors(type): instantiated and sets their name attribute. """ - def __new__(mcls, name, bases, classdict): + def __new__(mcls, name, bases, classdict): # noqa """Create the HasDescriptors class.""" for k, v in classdict.items(): # ---------------------------------------------------------------- @@ -976,7 +976,7 @@ def setup_class(cls, classdict): class MetaHasTraits(MetaHasDescriptors): """A metaclass for HasTraits.""" - def setup_class(cls, classdict): + def setup_class(cls, classdict): # noqa cls._trait_default_generators = {} super().setup_class(classdict) @@ -1800,10 +1800,10 @@ def trait_metadata(self, traitname, key, default=None): """Get metadata values for trait by key.""" try: trait = getattr(self.__class__, traitname) - except AttributeError: + except AttributeError as e: raise TraitError( f"Class {self.__class__.__name__} does not have a trait named {traitname}" - ) + ) from e metadata_name = "_" + traitname + "_metadata" if hasattr(self, metadata_name) and key in getattr(self, metadata_name): return getattr(self, metadata_name).get(key, default) @@ -1924,11 +1924,11 @@ def validate(self, obj, value): if isinstance(value, str): try: value = self._resolve_string(value) - except ImportError: + 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) - ) + ) from e try: if issubclass(value, self.klass): # type:ignore[arg-type] return value @@ -2394,9 +2394,9 @@ def validate(self, obj, value): if isinstance(value, bytes): try: return value.decode("ascii", "strict") - except UnicodeDecodeError: + except UnicodeDecodeError as e: msg = "Could not decode {!r} for unicode trait '{}' of {} instance." - raise TraitError(msg.format(value, self.name, class_of(obj))) + raise TraitError(msg.format(value, self.name, class_of(obj))) from e self.error(obj, value) def from_string(self, s): diff --git a/traitlets/utils/bunch.py b/traitlets/utils/bunch.py index 7982bbb9..6b3fffeb 100644 --- a/traitlets/utils/bunch.py +++ b/traitlets/utils/bunch.py @@ -13,8 +13,8 @@ class Bunch(dict): # type:ignore[type-arg] def __getattr__(self, key): try: return self.__getitem__(key) - except KeyError: - raise AttributeError(key) + except KeyError as e: + raise AttributeError(key) from e def __setattr__(self, key, value): self.__setitem__(key, value) diff --git a/traitlets/utils/getargspec.py b/traitlets/utils/getargspec.py index e2b1f235..7fe8d2cc 100644 --- a/traitlets/utils/getargspec.py +++ b/traitlets/utils/getargspec.py @@ -11,6 +11,7 @@ import inspect from functools import partial + # Unmodified from sphinx below this line diff --git a/traitlets/utils/importstring.py b/traitlets/utils/importstring.py index 7258e20b..7ac1e9ab 100644 --- a/traitlets/utils/importstring.py +++ b/traitlets/utils/importstring.py @@ -30,8 +30,8 @@ def import_item(name): module = __import__(package, fromlist=[obj]) try: pak = getattr(module, obj) - except AttributeError: - raise ImportError("No module named %s" % obj) + except AttributeError as e: + raise ImportError("No module named %s" % obj) from e return pak else: # called with un-dotted string From fd7bb14957177d01f974c8448ea7082a73e9fb40 Mon Sep 17 00:00:00 2001 From: Steven Silvester Date: Wed, 7 Dec 2022 09:26:13 -0600 Subject: [PATCH 2/2] lint --- traitlets/config/application.py | 17 ++++++++++------- traitlets/config/loader.py | 2 +- traitlets/config/tests/test_application.py | 8 ++------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/traitlets/config/application.py b/traitlets/config/application.py index b7c77270..66f56f72 100644 --- a/traitlets/config/application.py +++ b/traitlets/config/application.py @@ -366,7 +366,9 @@ def _log_default(self): # flags are loaded from this dict by '--key' flags # this must be a dict of two-tuples, the first element being the Config/dict # and the second being the help string for the flag - flags: t.Dict[t.Union[str, t.Tuple[str, ...]], t.Tuple[t.Union[t.Dict, Config], str]] = { + flags: t.Dict[ + t.Union[str, t.Tuple[str, ...]], t.Tuple[t.Union[t.Dict[str, t.Any], Config], str] + ] = { "debug": ( { "Application": { @@ -537,12 +539,13 @@ def emit_alias_help(self): fhelp = cls.class_get_trait_help(trait, helptext=fhelp).splitlines() if not isinstance(alias, tuple): - alias = (alias,) # type:ignore[assignment] + alias = (alias,) alias = sorted(alias, key=len) # type:ignore[assignment] alias = ", ".join(("--%s" if len(m) > 1 else "-%s") % m for m in alias) # reformat first line - fhelp[0] = fhelp[0].replace("--" + longname, alias) + assert fhelp is not None + fhelp[0] = fhelp[0].replace("--" + longname, alias) # type:ignore yield from fhelp yield indent("Equivalent to: [--%s]" % longname) except Exception as ex: @@ -561,7 +564,7 @@ def emit_flag_help(self): for flags, (cfg, fhelp) in self.flags.items(): try: if not isinstance(flags, tuple): - flags = (flags,) # type:ignore[assignment] + flags = (flags,) flags = sorted(flags, key=len) # type:ignore[assignment] flags = ", ".join(("--%s" if len(m) > 1 else "-%s") % m for m in flags) yield flags @@ -748,7 +751,7 @@ def flatten_flags(self): for alias, longname in self.aliases.items(): if isinstance(longname, tuple): longname, _ = longname - cls, trait = longname.split(".", 1) + cls, trait = longname.split(".", 1) # type:ignore children = mro_tree[cls] # type:ignore[index] if len(children) == 1: # exactly one descendent, promote alias @@ -763,7 +766,7 @@ def flatten_flags(self): flags = {} for key, (flagdict, help) in self.flags.items(): newflag: t.Dict[t.Any, t.Any] = {} - for cls, subdict in flagdict.items(): + for cls, subdict in flagdict.items(): # type:ignore children = mro_tree[cls] # type:ignore[index] # exactly one descendent, promote flag section if len(children) == 1: @@ -775,7 +778,7 @@ def flatten_flags(self): newflag[cls] = subdict if not isinstance(key, tuple): - key = (key,) # type:ignore[assignment] + key = (key,) for k in key: flags[k] = (newflag, help) return flags, aliases diff --git a/traitlets/config/loader.py b/traitlets/config/loader.py index 14c22b5f..414912b7 100644 --- a/traitlets/config/loader.py +++ b/traitlets/config/loader.py @@ -1075,7 +1075,7 @@ def _convert_to_config(self): for subc in self.parsed_data._flags: self._load_flag(subc) - def _handle_unrecognized_alias(self, arg: str): + def _handle_unrecognized_alias(self, arg: str) -> None: """Handling for unrecognized alias arguments Probably a mistyped alias. By default just log a warning, diff --git a/traitlets/config/tests/test_application.py b/traitlets/config/tests/test_application.py index 70e4a08f..16224683 100644 --- a/traitlets/config/tests/test_application.py +++ b/traitlets/config/tests/test_application.py @@ -23,11 +23,7 @@ from traitlets.config.application import Application from traitlets.config.configurable import Configurable from traitlets.config.loader import Config, KVArgParseConfigLoader -from traitlets.tests.utils import ( - check_help_all_output, - check_help_output, - get_output_error_code, -) +from traitlets.tests.utils import check_help_all_output, check_help_output, get_output_error_code try: from unittest import mock @@ -376,7 +372,7 @@ def test_aliases_multiple(self): # Test multiple > 2 aliases for the same argument class TestMultiAliasApp(Application): foo = Integer(config=True) - aliases = {("f", "bar", "qux"): "TestMultiAliasApp.foo"} # type:ignore + aliases = {("f", "bar", "qux"): "TestMultiAliasApp.foo"} app = TestMultiAliasApp() app.parse_command_line(["-f", "3"])