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)