From 98ee4b6c8e3266d637644a00c3e9a19de8c21e73 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Wed, 28 Jan 2026 12:34:04 -0500 Subject: [PATCH 1/6] [_708] new __slots__ members to prevent configuration misfires --- irods/client_configuration/__init__.py | 6 +++++ irods/test/client_configuration_test.py | 33 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 irods/test/client_configuration_test.py diff --git a/irods/client_configuration/__init__.py b/irods/client_configuration/__init__.py index 505b48b0d..4260f309b 100644 --- a/irods/client_configuration/__init__.py +++ b/irods/client_configuration/__init__.py @@ -45,6 +45,8 @@ def __new__(meta, name, bases, attrs): class ConnectionsProperties(iRODSConfiguration, metaclass=iRODSConfigAliasMetaclass): + __slots__=() + @property def xml_parser_default(self): from irods.message import get_default_XML_by_name @@ -59,10 +61,13 @@ def xml_parser_default(self, str_value): connections = ConnectionsProperties() + class ConfigurationError(BaseException): pass class ConfigurationValueError(ValueError,ConfigurationError): pass + class Genquery1_Properties(iRODSConfiguration, metaclass=iRODSConfigAliasMetaclass): + __slots__ = () @property def irods_query_limit(self): @@ -89,6 +94,7 @@ def irods_query_limit(self, target_value): class DataObjects(iRODSConfiguration): + __slots__ = ( "auto_close", "allow_redirect", diff --git a/irods/test/client_configuration_test.py b/irods/test/client_configuration_test.py new file mode 100644 index 000000000..5fa7739b0 --- /dev/null +++ b/irods/test/client_configuration_test.py @@ -0,0 +1,33 @@ +import unittest + +import irods.client_configuration as cfg + + +# Test assignments on the negative and positive space of the +# client configuration. +class TestClientConfigurationAttributes(unittest.TestCase): + def test_configuration_writes_and_miswrites__issue_708(self): + # For caching configuration objects + configuration_level = {} + leaf_names = [] + + for dotted_name, value, is_conf in cfg._var_items_as_generator(): + with self.subTest(dotted_name=dotted_name): + name_parts = dotted_name.split('.') + namespace = '.'.join(name_parts[:-1]) + attribute_name = name_parts[-1] + if isinstance(value, cfg.iRODSConfiguration): + configuration_level[dotted_name] = value + elif is_conf: + leaf_names.append(attribute_name) + + # Test the positive space, i.e. the 'hit' + setattr(configuration_level[namespace], attribute_name, value) + + # Test the negative space, i.e. the 'miss' + with self.assertRaises(AttributeError): + setattr(configuration_level[namespace], attribute_name + '_1', value) + + # These cases were identified as likely ones for possible failed writes via misspelling. + self.assertIn('irods_query_limit', leaf_names) + self.assertIn('xml_parser_default', leaf_names) From 994ff711f7a0b030f893cf545ce714f6846f9211 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Thu, 19 Feb 2026 04:15:12 -0500 Subject: [PATCH 2/6] ruff: whitespace --- irods/client_configuration/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irods/client_configuration/__init__.py b/irods/client_configuration/__init__.py index 4260f309b..ffdfe9ec4 100644 --- a/irods/client_configuration/__init__.py +++ b/irods/client_configuration/__init__.py @@ -45,7 +45,7 @@ def __new__(meta, name, bases, attrs): class ConnectionsProperties(iRODSConfiguration, metaclass=iRODSConfigAliasMetaclass): - __slots__=() + __slots__ = () @property def xml_parser_default(self): From fb14c3478b002c79cd4543da1c98bfc431b001b5 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Thu, 19 Feb 2026 04:19:30 -0500 Subject: [PATCH 3/6] adding noqa notation --- irods/test/client_configuration_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irods/test/client_configuration_test.py b/irods/test/client_configuration_test.py index 5fa7739b0..29417a132 100644 --- a/irods/test/client_configuration_test.py +++ b/irods/test/client_configuration_test.py @@ -11,7 +11,7 @@ def test_configuration_writes_and_miswrites__issue_708(self): configuration_level = {} leaf_names = [] - for dotted_name, value, is_conf in cfg._var_items_as_generator(): + for dotted_name, value, is_conf in cfg._var_items_as_generator(): # noqa: SLF001 with self.subTest(dotted_name=dotted_name): name_parts = dotted_name.split('.') namespace = '.'.join(name_parts[:-1]) From 7dd29120d25206400a2eb517ad59c79511e6dd83 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Thu, 19 Feb 2026 18:45:08 -0500 Subject: [PATCH 4/6] extra space before # noqa --- irods/test/client_configuration_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irods/test/client_configuration_test.py b/irods/test/client_configuration_test.py index 29417a132..4e40e6612 100644 --- a/irods/test/client_configuration_test.py +++ b/irods/test/client_configuration_test.py @@ -11,7 +11,7 @@ def test_configuration_writes_and_miswrites__issue_708(self): configuration_level = {} leaf_names = [] - for dotted_name, value, is_conf in cfg._var_items_as_generator(): # noqa: SLF001 + for dotted_name, value, is_conf in cfg._var_items_as_generator(): # noqa: SLF001 with self.subTest(dotted_name=dotted_name): name_parts = dotted_name.split('.') namespace = '.'.join(name_parts[:-1]) From f4c5a62aecdbbf1a1aa4165ce21a1b241814b634 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Thu, 19 Feb 2026 19:16:13 -0500 Subject: [PATCH 5/6] comments/explanations --- irods/test/client_configuration_test.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/irods/test/client_configuration_test.py b/irods/test/client_configuration_test.py index 4e40e6612..ef828c76f 100644 --- a/irods/test/client_configuration_test.py +++ b/irods/test/client_configuration_test.py @@ -17,14 +17,24 @@ def test_configuration_writes_and_miswrites__issue_708(self): namespace = '.'.join(name_parts[:-1]) attribute_name = name_parts[-1] if isinstance(value, cfg.iRODSConfiguration): + # Store a parent object corresponding to a namespace. For any leaf value + # subsequently found in the top-down descent, to be sitting directly within + # that namespace, the "else is_conf" part of this if/else will run the core + # part of the test on the corresponding configuration setting. configuration_level[dotted_name] = value elif is_conf: + # A configuration setting was actually found (i.e. a "leaf" value within a dotted name.) + + # Store the leaf name for proof positive of subtests actually run. leaf_names.append(attribute_name) - # Test the positive space, i.e. the 'hit' + # Test the positive space, i.e. the 'hit'. This simply tests that the + # setting may be written to without error: setattr(configuration_level[namespace], attribute_name, value) - # Test the negative space, i.e. the 'miss' + # Test the negative space, i.e. a deliberate 'miss'. This time we must fail; + # otherwise we'd get a silent miswrite in the form of a write to the incorrect attribute. + # (The new __slots__ members are there to prevent this): with self.assertRaises(AttributeError): setattr(configuration_level[namespace], attribute_name + '_1', value) From 31aea141ffe7d16197efa432a19fc9cef68aa916 Mon Sep 17 00:00:00 2001 From: d-w-moore Date: Fri, 20 Feb 2026 05:00:10 -0500 Subject: [PATCH 6/6] trailing ws (ruff) --- irods/test/client_configuration_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/irods/test/client_configuration_test.py b/irods/test/client_configuration_test.py index ef828c76f..4c61625ef 100644 --- a/irods/test/client_configuration_test.py +++ b/irods/test/client_configuration_test.py @@ -17,7 +17,7 @@ def test_configuration_writes_and_miswrites__issue_708(self): namespace = '.'.join(name_parts[:-1]) attribute_name = name_parts[-1] if isinstance(value, cfg.iRODSConfiguration): - # Store a parent object corresponding to a namespace. For any leaf value + # Store a parent object corresponding to a namespace. For any leaf value # subsequently found in the top-down descent, to be sitting directly within # that namespace, the "else is_conf" part of this if/else will run the core # part of the test on the corresponding configuration setting.