diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ace88b40..98fa2117 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.10", "3.11", "3.12"] + python-version: ["3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml deleted file mode 100644 index 6c463eed..00000000 --- a/.github/workflows/lint.yaml +++ /dev/null @@ -1,16 +0,0 @@ -name: lint - -on: - push: - branches: - - main - pull_request: - -jobs: - call-workflow: - uses: lsst/rubin_workflows/.github/workflows/lint.yaml@main - ruff: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: chartboost/ruff-action@v1 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ead91c0e..1d020b89 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,34 +1,19 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: check-yaml - id: end-of-file-fixer - id: trailing-whitespace - - repo: https://github.com/psf/black - rev: 24.4.2 - hooks: - - id: black - # It is recommended to specify the latest version of Python - # supported by your project here, or alternatively use - # pre-commit's default_language_version, see - # https://pre-commit.com/#top_level-default_language_version - language_version: python3.10 - - repo: https://github.com/pycqa/isort - rev: 5.13.2 - hooks: - - id: isort - name: isort (python) - - repo: https://github.com/PyCQA/flake8 - rev: 7.1.0 - hooks: - - id: flake8 + - id: check-toml - repo: https://github.com/astral-sh/ruff-pre-commit # Ruff version. - rev: v0.5.1 + rev: v0.9.4 hooks: - id: ruff + args: [--fix] + - id: ruff-format - repo: https://github.com/numpy/numpydoc - rev: "v1.7.0" + rev: "v1.8.0" hooks: - id: numpydoc-validation diff --git a/doc/changes/DM-48074.feature.rst b/doc/changes/DM-48074.feature.rst index bec51c21..fc63bef7 100644 --- a/doc/changes/DM-48074.feature.rst +++ b/doc/changes/DM-48074.feature.rst @@ -1 +1 @@ -Introduced the `keyCheck` callback to `DictField` and `ConfigDictField`, allowing custom key validation during assignment. Added unit tests to ensure functionality. \ No newline at end of file +Introduced the `keyCheck` callback to `DictField` and `ConfigDictField`, allowing custom key validation during assignment. Added unit tests to ensure functionality. diff --git a/pyproject.toml b/pyproject.toml index 81f805d5..089c73ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,7 @@ target-version = ["py310"] [tool.isort] profile = "black" line_length = 110 +known_first_party = ["lsst"] [tool.towncrier] package = "lsst.pex.config" @@ -149,6 +150,10 @@ select = [ "W", # pycodestyle "D", # pydocstyle "UP", # pyupgrade + "I", # isort + "RUF022", # sort __all__ + "C4", # comprehensions + "B", # bugbear ] extend-select = [ "RUF100", # Warn about unused noqa @@ -157,12 +162,20 @@ extend-select = [ [tool.ruff.lint.per-file-ignores] "tests/testLib.py" = ["F403", "F405"] # Wildcard imports. +[tool.ruff.lint.isort] +known-first-party = ["lsst"] + [tool.ruff.lint.pycodestyle] max-doc-length = 79 [tool.ruff.lint.pydocstyle] convention = "numpy" +[tool.ruff.format] +docstring-code-format = true +# Formatter does not know about indenting. +docstring-code-line-length = 69 + [tool.numpydoc_validation] checks = [ "all", # All except the rules listed below. diff --git a/python/lsst/pex/config/callStack.py b/python/lsst/pex/config/callStack.py index a968bb66..218ae0f7 100644 --- a/python/lsst/pex/config/callStack.py +++ b/python/lsst/pex/config/callStack.py @@ -25,7 +25,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -__all__ = ["getCallerFrame", "getStackFrame", "StackFrame", "getCallStack"] +__all__ = ["StackFrame", "getCallStack", "getCallerFrame", "getStackFrame"] import inspect import linecache @@ -50,7 +50,7 @@ def getCallerFrame(relative=0): This function is excluded from the frame. """ frame = inspect.currentframe().f_back.f_back # Our caller's caller - for ii in range(relative): + for _ in range(relative): frame = frame.f_back return frame diff --git a/python/lsst/pex/config/comparison.py b/python/lsst/pex/config/comparison.py index 3ffba6c6..9e35fe8d 100644 --- a/python/lsst/pex/config/comparison.py +++ b/python/lsst/pex/config/comparison.py @@ -32,7 +32,7 @@ writing messages as well as floating-point comparisons and shortcuts. """ -__all__ = ("getComparisonName", "compareScalars", "compareConfigs") +__all__ = ("compareConfigs", "compareScalars", "getComparisonName") import numpy diff --git a/python/lsst/pex/config/config.py b/python/lsst/pex/config/config.py index 19928cc0..27e774e2 100644 --- a/python/lsst/pex/config/config.py +++ b/python/lsst/pex/config/config.py @@ -30,9 +30,9 @@ "Config", "ConfigMeta", "Field", + "FieldTypeVar", "FieldValidationError", "UnexpectedProxyUsageError", - "FieldTypeVar", ) import copy @@ -390,7 +390,6 @@ class Field(Generic[FieldTypeVar]): >>> class Example(Config): ... myInt = Field("An integer field.", int, default=0) ... name = Field[str](doc="A string Field") - ... >>> print(config.myInt) 0 >>> config.myInt = 5 @@ -730,13 +729,14 @@ def __get__(self, instance, owner=None, at=None, label="default"): # try statements are almost free in python if they succeed try: return instance._storage[self.name] - except AttributeError: + except AttributeError as e: if not isinstance(instance, Config): return self else: - raise AttributeError( + e.add_note( f"Config {instance} is missing _storage attribute, likely incorrectly initialized" ) + raise def __set__( self, instance: Config, value: FieldTypeVar | None, at: Any = None, label: str = "assignment" @@ -791,7 +791,7 @@ def __set__( try: self._validateValue(value) except BaseException as e: - raise FieldValidationError(self, instance, str(e)) + raise FieldValidationError(self, instance, str(e)) from e instance._storage[self.name] = value if at is None: @@ -943,23 +943,25 @@ class behavior. >>> from lsst.pex.config import Config, Field, ListField >>> class DemoConfig(Config): ... intField = Field(doc="An integer field", dtype=int, default=42) - ... listField = ListField(doc="List of favorite beverages.", dtype=str, - ... default=['coffee', 'green tea', 'water']) - ... + ... listField = ListField( + ... doc="List of favorite beverages.", + ... dtype=str, + ... default=["coffee", "green tea", "water"], + ... ) >>> config = DemoConfig() Configs support many `dict`-like APIs: >>> config.keys() ['intField', 'listField'] - >>> 'intField' in config + >>> "intField" in config True Individual fields can be accessed as attributes of the configuration: >>> config.intField 42 - >>> config.listField.append('earl grey tea') + >>> config.listField.append("earl grey tea") >>> print(config.listField) ['coffee', 'green tea', 'water', 'earl grey tea'] """ @@ -1102,30 +1104,27 @@ def update(self, **kw): >>> from lsst.pex.config import Config, Field >>> class DemoConfig(Config): - ... fieldA = Field(doc='Field A', dtype=int, default=42) - ... fieldB = Field(doc='Field B', dtype=bool, default=True) - ... fieldC = Field(doc='Field C', dtype=str, default='Hello world') - ... + ... fieldA = Field(doc="Field A", dtype=int, default=42) + ... fieldB = Field(doc="Field B", dtype=bool, default=True) + ... fieldC = Field(doc="Field C", dtype=str, default="Hello world") >>> config = DemoConfig() These are the default values of each field: >>> for name, value in config.iteritems(): ... print(f"{name}: {value}") - ... fieldA: 42 fieldB: True fieldC: 'Hello world' Using this method to update ``fieldA`` and ``fieldC``: - >>> config.update(fieldA=13, fieldC='Updated!') + >>> config.update(fieldA=13, fieldC="Updated!") Now the values of each field are: >>> for name, value in config.iteritems(): ... print(f"{name}: {value}") - ... fieldA: 13 fieldB: True fieldC: 'Updated!' @@ -1137,8 +1136,9 @@ def update(self, **kw): try: field = self._fields[name] field.__set__(self, value, at=at, label=label) - except KeyError: - raise KeyError(f"No field of name {name} exists in config type {_typeStr(self)}") + except KeyError as e: + e.add_note(f"No field of name {name} exists in config type {_typeStr(self)}") + raise def load(self, filename, root="config"): """Modify this config in place by executing the Python code in a @@ -1415,7 +1415,7 @@ def _collectImports(self): class. """ self._imports.add(self.__module__) - for name, field in self._fields.items(): + for field in self._fields.values(): field._collectImports(self, self._imports) def toDict(self): diff --git a/python/lsst/pex/config/configChoiceField.py b/python/lsst/pex/config/configChoiceField.py index fa0f3549..973a55d5 100644 --- a/python/lsst/pex/config/configChoiceField.py +++ b/python/lsst/pex/config/configChoiceField.py @@ -76,9 +76,9 @@ def __init__(self, dict_, value, at=None, label="assignment", setHistory=True): if v not in self._dict: # invoke __getitem__ to ensure it's present self._dict.__getitem__(v, at=at) - except TypeError: + except TypeError as e: msg = f"Value {value} is of incorrect type {_typeStr(value)}. Sequence type expected" - raise FieldValidationError(self._field, self._config, msg) + raise FieldValidationError(self._field, self._config, msg) from e self._set = set(value) else: self._set = set() @@ -293,10 +293,10 @@ def __getitem__(self, k, at=None, label="default"): except KeyError: try: dtype = self.types[k] - except Exception: + except Exception as e: raise FieldValidationError( self._field, self._config, f"Unknown key {k!r} in Registry/ConfigChoiceField" - ) + ) from e name = _joinNamePath(self._config._name, self._field.name, k) if at is None: at = getCallStack() @@ -310,8 +310,8 @@ def __setitem__(self, k, value, at=None, label="assignment"): try: dtype = self.types[k] - except Exception: - raise FieldValidationError(self._field, self._config, f"Unknown key {k!r}") + except Exception as e: + raise FieldValidationError(self._field, self._config, f"Unknown key {k!r}") from e if value != dtype and type(value) is not dtype: msg = ( @@ -443,7 +443,6 @@ class ConfigChoiceField(Field[ConfigInstanceDict]): >>> from lsst.pex.config import Config, ConfigChoiceField, Field >>> class AaaConfig(Config): ... somefield = Field("doc", int) - ... The ``MyConfig`` config has a ``ConfigChoiceField`` field called ``choice`` that maps the ``AaaConfig`` type to the ``"AAA"`` key: @@ -451,7 +450,6 @@ class ConfigChoiceField(Field[ConfigInstanceDict]): >>> TYPEMAP = {"AAA", AaaConfig} >>> class MyConfig(Config): ... choice = ConfigChoiceField("doc for choice", TYPEMAP) - ... Creating an instance of ``MyConfig``: @@ -460,7 +458,7 @@ class ConfigChoiceField(Field[ConfigInstanceDict]): Setting value of the field ``somefield`` on the "AAA" key of the ``choice`` field: - >>> instance.choice['AAA'].somefield = 5 + >>> instance.choice["AAA"].somefield = 5 **Selecting the active configuration** diff --git a/python/lsst/pex/config/configDictField.py b/python/lsst/pex/config/configDictField.py index 91f8f2d7..98b9626a 100644 --- a/python/lsst/pex/config/configDictField.py +++ b/python/lsst/pex/config/configDictField.py @@ -201,7 +201,7 @@ def __init__( check_errors = [] for name, check in (("dictCheck", dictCheck), ("keyCheck", keyCheck), ("itemCheck", itemCheck)): - if check is not None and not hasattr(check, "__call__"): + if check is not None and not callable(check): check_errors.append(name) if check_errors: raise ValueError(f"{', '.join(check_errors)} must be callable") diff --git a/python/lsst/pex/config/configurableActions/_configurableAction.py b/python/lsst/pex/config/configurableActions/_configurableAction.py index 14331046..88dac083 100644 --- a/python/lsst/pex/config/configurableActions/_configurableAction.py +++ b/python/lsst/pex/config/configurableActions/_configurableAction.py @@ -20,7 +20,7 @@ # along with this program. If not, see . from __future__ import annotations -__all__ = ["ConfigurableAction", "ActionTypeVar"] +__all__ = ["ActionTypeVar", "ConfigurableAction"] from typing import Any, TypeVar diff --git a/python/lsst/pex/config/configurableActions/_configurableActionStructField.py b/python/lsst/pex/config/configurableActions/_configurableActionStructField.py index 2f044919..d8f1148b 100644 --- a/python/lsst/pex/config/configurableActions/_configurableActionStructField.py +++ b/python/lsst/pex/config/configurableActions/_configurableActionStructField.py @@ -20,7 +20,7 @@ # along with this program. If not, see . from __future__ import annotations -__all__ = ("ConfigurableActionStructField", "ConfigurableActionStruct") +__all__ = ("ConfigurableActionStruct", "ConfigurableActionStructField") import weakref from collections.abc import Iterable, Iterator, Mapping @@ -55,7 +55,7 @@ def __set__( value = value._attrs else: raise ValueError( - "Can only update a ConfigurableActionStruct with an instance of such, or a " "mapping" + "Can only update a ConfigurableActionStruct with an instance of such, or a mapping" ) for name, action in value.items(): setattr(instance, name, action) @@ -196,7 +196,7 @@ def __setattr__( setHistory=False, ) -> None: if hasattr(self._config, "_frozen") and self._config._frozen: - msg = "Cannot modify a frozen Config. " f"Attempting to set item {attr} to value {value}" + msg = f"Cannot modify a frozen Config. Attempting to set item {attr} to value {value}" raise FieldValidationError(self._field, self._config, msg) # verify that someone has not passed a string with a space or leading @@ -315,7 +315,7 @@ def __set__( label: str = "assigment", ): if instance._frozen: - msg = "Cannot modify a frozen Config. " f"Attempting to set field to value {value}" + msg = f"Cannot modify a frozen Config. Attempting to set field to value {value}" raise FieldValidationError(self, instance, msg) if at is None: diff --git a/python/lsst/pex/config/configurableActions/tests.py b/python/lsst/pex/config/configurableActions/tests.py index 5792b0eb..41c4e28b 100644 --- a/python/lsst/pex/config/configurableActions/tests.py +++ b/python/lsst/pex/config/configurableActions/tests.py @@ -24,9 +24,9 @@ "ActionTest1", "ActionTest2", "ActionTest3", + "TestConfig", "TestDivideAction", "TestSingleColumnAction", - "TestConfig", ) from lsst.pex.config import Config, Field diff --git a/python/lsst/pex/config/configurableField.py b/python/lsst/pex/config/configurableField.py index 0acd9877..b32da0e1 100644 --- a/python/lsst/pex/config/configurableField.py +++ b/python/lsst/pex/config/configurableField.py @@ -27,7 +27,7 @@ from __future__ import annotations -__all__ = ("ConfigurableInstance", "ConfigurableField") +__all__ = ("ConfigurableField", "ConfigurableInstance") import copy import weakref @@ -166,7 +166,7 @@ def retarget(self, target, ConfigClass=None, at=None, label="retarget"): try: ConfigClass = self._field.validateTarget(target, ConfigClass) except BaseException as e: - raise FieldValidationError(self._field, self._config, e.message) + raise FieldValidationError(self._field, self._config, e.message) from e if at is None: at = getCallStack() @@ -304,14 +304,14 @@ def validateTarget(self, target, ConfigClass): if ConfigClass is None: try: ConfigClass = target.ConfigClass - except Exception: - raise AttributeError("'target' must define attribute 'ConfigClass'") + except Exception as e: + raise AttributeError("'target' must define attribute 'ConfigClass'") from e if not issubclass(ConfigClass, Config): raise TypeError( f"'ConfigClass' is of incorrect type {_typeStr(ConfigClass)}. " "'ConfigClass' must be a subclass of Config" ) - if not hasattr(target, "__call__"): + if not callable(target): raise ValueError("'target' must be callable") if not hasattr(target, "__module__") or not hasattr(target, "__name__"): raise ValueError( diff --git a/python/lsst/pex/config/dictField.py b/python/lsst/pex/config/dictField.py index 45c739c7..ba53a254 100644 --- a/python/lsst/pex/config/dictField.py +++ b/python/lsst/pex/config/dictField.py @@ -82,9 +82,9 @@ def __init__(self, config, field, value, at, label, setHistory=True): for k in value: # do not set history per-item self.__setitem__(k, value[k], at=at, label=label, setHistory=False) - except TypeError: + except TypeError as e: msg = f"Value {value} is of incorrect type {_typeStr(value)}. Mapping type expected." - raise FieldValidationError(self._field, self._config, msg) + raise FieldValidationError(self._field, self._config, msg) from e if setHistory: self._history.append((dict(self._dict), at, label)) @@ -247,11 +247,12 @@ class DictField(Field[Dict[KeyTypeVar, ItemTypeVar]], Generic[KeyTypeVar, ItemTy >>> class MyConfig(Config): ... field = DictField( ... doc="Example string-to-int mapping field.", - ... keytype=str, itemtype=int, - ... default={}) - ... + ... keytype=str, + ... itemtype=int, + ... default={}, + ... ) >>> config = MyConfig() - >>> config.field['myKey'] = 42 + >>> config.field["myKey"] = 42 >>> print(config.field) {'myKey': 42} """ @@ -321,7 +322,7 @@ def __init__( check_errors = [] for name, check in (("dictCheck", dictCheck), ("keyCheck", keyCheck), ("itemCheck", itemCheck)): - if check is not None and not hasattr(check, "__call__"): + if check is not None and not callable(check): check_errors.append(name) if check_errors: raise ValueError(f"{', '.join(check_errors)} must be callable") diff --git a/python/lsst/pex/config/history.py b/python/lsst/pex/config/history.py index 406e4711..7ccd0f25 100644 --- a/python/lsst/pex/config/history.py +++ b/python/lsst/pex/config/history.py @@ -96,8 +96,8 @@ class Color: def __init__(self, text, category): try: color = Color.categories[category] - except KeyError: - raise RuntimeError(f"Unknown category: {category}") + except KeyError as e: + raise RuntimeError(f"Unknown category: {category}") from e self.rawText = str(text) x = color.lower().split(";") @@ -109,8 +109,8 @@ def __init__(self, text, category): try: self._code = "%s" % (30 + Color.colors[self.color]) - except KeyError: - raise RuntimeError(f"Unknown colour: {self.color}") + except KeyError as e: + raise RuntimeError(f"Unknown colour: {self.color}") from e if bold: self._code += ";1" @@ -197,7 +197,7 @@ def format(config, name=None, writeSourceLine=True, prefix="", verbose=False): print(format(config, name)) outputs = [] - for value, stack, label in config.history.get(name, []): + for value, stack, _ in config.history.get(name, []): output = [] for frame in stack: if frame.function in ( @@ -241,11 +241,11 @@ def format(config, name=None, writeSourceLine=True, prefix="", verbose=False): # Find the maximum widths of the value and file:lineNo fields. if writeSourceLine: sourceLengths = [] - for value, output in outputs: + for _, output in outputs: sourceLengths.append(max([len(x[0][0]) for x in output])) sourceLength = max(sourceLengths) - valueLength = len(prefix) + max([len(str(value)) for value, output in outputs]) + valueLength = len(prefix) + max([len(str(value)) for value, _ in outputs]) # Generate the config history content. msg = [] diff --git a/python/lsst/pex/config/listField.py b/python/lsst/pex/config/listField.py index 809c6d2e..628b6ef3 100644 --- a/python/lsst/pex/config/listField.py +++ b/python/lsst/pex/config/listField.py @@ -30,6 +30,7 @@ import collections.abc import weakref from collections.abc import Iterable, MutableSequence +from itertools import zip_longest from typing import Any, Generic, overload from .callStack import getCallStack, getStackFrame @@ -83,9 +84,9 @@ def __init__(self, config, field, value, at, label, setHistory=True): try: for i, x in enumerate(value): self.insert(i, x, setHistory=False) - except TypeError: + except TypeError as e: msg = f"Value {value} is of incorrect type {_typeStr(value)}. Sequence type expected" - raise FieldValidationError(self._field, config, msg) + raise FieldValidationError(self._field, config, msg) from e if setHistory: self.history.append((list(self._list), at, label)) @@ -229,7 +230,7 @@ def __eq__(self, other): if len(self) != len(other): return False - for i, j in zip(self, other): + for i, j in zip_longest(self, other): if i != j: return False return True @@ -338,9 +339,9 @@ def __init__( f"'maxLength' ({maxLength}) must be at least as large as 'minLength' ({minLength})" ) - if listCheck is not None and not hasattr(listCheck, "__call__"): + if listCheck is not None and not callable(listCheck): raise ValueError("'listCheck' must be callable") - if itemCheck is not None and not hasattr(itemCheck, "__call__"): + if itemCheck is not None and not callable(itemCheck): raise ValueError("'itemCheck' must be callable") source = getStackFrame() @@ -506,7 +507,7 @@ def _compare(self, instance1, instance2, shortcut, rtol, atol, output): if not compareScalars(f"size for {name}", len(l1), len(l2), output=output): return False equal = True - for n, v1, v2 in zip(range(len(l1)), l1, l2): + for n, v1, v2 in zip(range(len(l1)), l1, l2, strict=False): result = compareScalars( f"{name}[{n}]", v1, v2, dtype=self.dtype, rtol=rtol, atol=atol, output=output ) diff --git a/python/lsst/pex/config/registry.py b/python/lsst/pex/config/registry.py index 18d1db96..895ffe25 100644 --- a/python/lsst/pex/config/registry.py +++ b/python/lsst/pex/config/registry.py @@ -25,7 +25,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -__all__ = ("Registry", "makeRegistry", "RegistryField", "registerConfig", "registerConfigurable") +__all__ = ("Registry", "RegistryField", "makeRegistry", "registerConfig", "registerConfigurable") import collections.abc import copy @@ -92,14 +92,14 @@ class Registry(collections.abc.Mapping): >>> from lsst.pex.config import Registry, Config >>> class FooConfig(Config): ... val = Field(dtype=int, default=3, doc="parameter for Foo") - ... >>> class Foo: ... ConfigClass = FooConfig + ... ... def __init__(self, config): ... self.config = config + ... ... def addVal(self, num): ... return self.config.val + num - ... Next, create a ``Registry`` instance called ``registry`` and register the ``Foo`` configurable under the ``"foo"`` key: diff --git a/python/lsst/pex/config/wrap.py b/python/lsst/pex/config/wrap.py index 28d79e64..e8192795 100644 --- a/python/lsst/pex/config/wrap.py +++ b/python/lsst/pex/config/wrap.py @@ -25,7 +25,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -__all__ = ("wrap", "makeConfigClass") +__all__ = ("makeConfigClass", "wrap") import importlib import inspect @@ -118,7 +118,9 @@ def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=None, cls=Non import lsst.pex.config import myWrappedLib - InnerConfig = lsst.pex.config.makeConfigClass(myWrappedLib.InnerControl) + InnerConfig = lsst.pex.config.makeConfigClass( + myWrappedLib.InnerControl + ) FooConfig = lsst.pex.config.makeConfigClass(myWrappedLib.FooControl) This does the following things: @@ -199,8 +201,8 @@ def makeConfigClass(ctrl, name=None, base=Config, doc=None, module=None, cls=Non nestedModuleObj = importlib.import_module(nestedModuleName) try: dtype = getattr(nestedModuleObj, ctype).ConfigClass - except AttributeError: - raise AttributeError(f"'{moduleName}.{ctype}.ConfigClass' does not exist") + except AttributeError as e: + raise AttributeError(f"'{moduleName}.{ctype}.ConfigClass' does not exist") from e fields[k] = ConfigField(doc=doc, dtype=dtype) else: try: diff --git a/tests/testLib.py b/tests/testLib.py index db9a8432..dce59924 100644 --- a/tests/testLib.py +++ b/tests/testLib.py @@ -27,9 +27,10 @@ import sys -import lsst.pex.config from _testLib import * +import lsst.pex.config + module = sys.modules[__name__] InnerConfigObject = lsst.pex.config.makeConfigClass(InnerControlObject, module=module) OuterConfigObject = lsst.pex.config.makeConfigClass(OuterControlObject, module=module) diff --git a/tests/test_Config.py b/tests/test_Config.py index fc4cbf63..8d0da204 100644 --- a/tests/test_Config.py +++ b/tests/test_Config.py @@ -217,12 +217,8 @@ def testValidate(self): self.outer.i.f = 10.0 self.outer.validate() - try: + with self.assertRaises(pexConfig.FieldValidationError): self.simple.d["failKey"] = "failValue" - except pexConfig.FieldValidationError: - pass - except Exception: - raise "Validation error Expected" self.simple.validate() self.outer.i = InnerConfig @@ -326,12 +322,12 @@ class Cfg2(pexConfig.Config): inclusiveMax=inclusiveMax, ) - if shouldRaise: - self.assertRaises(pexConfig.FieldValidationError, Cfg1) - self.assertRaises(pexConfig.FieldValidationError, Cfg2) - else: - Cfg1() - Cfg2() + if shouldRaise: + self.assertRaises(pexConfig.FieldValidationError, Cfg1) + self.assertRaises(pexConfig.FieldValidationError, Cfg2) + else: + Cfg1() + Cfg2() def testSave(self): self.comp.r = "BBB" @@ -601,7 +597,7 @@ def testIteration(self): self.assertIn("Hello", self.simple.values()) self.assertEqual(len(self.simple.values()), 8) - for k, v, (k1, v1) in zip(self.simple.keys(), self.simple.values(), self.simple.items()): + for k, v, (k1, v1) in zip(self.simple.keys(), self.simple.values(), self.simple.items(), strict=True): self.assertEqual(k, k1) if k == "n": self.assertNotEqual(v, v1)