From 4e8ef42c2534de210adb77105df831b82609c483 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Wed, 14 Feb 2024 10:55:21 -0700 Subject: [PATCH 1/5] First draft of private data on Blocks --- pyomo/core/base/block.py | 12 ++++++++++++ pyomo/core/tests/unit/test_block.py | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 89e872ebbe5..d10754082bd 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -550,6 +550,7 @@ def __init__(self, component): super(_BlockData, self).__setattr__('_ctypes', {}) super(_BlockData, self).__setattr__('_decl', {}) super(_BlockData, self).__setattr__('_decl_order', []) + self._private_data_dict = None def __getattr__(self, val): if val in ModelComponentFactory: @@ -2241,6 +2242,17 @@ def display(self, filename=None, ostream=None, prefix=""): for key in sorted(self): _BlockData.display(self[key], filename, ostream, prefix) + @property + def _private_data(self): + if self._private_data_dict is None: + self._private_data_dict = {} + return self._private_data_dict + + def private_data(self, scope): + if scope not in self._private_data: + self._private_data[scope] = {} + return self._private_data[scope] + class ScalarBlock(_BlockData, Block): def __init__(self, *args, **kwds): diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index f68850d9421..803237b1588 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -3407,6 +3407,21 @@ def test_deduplicate_component_data_iterindex(self): ], ) + def test_private_data(self): + m = ConcreteModel() + m.b = Block() + m.b.b = Block([1, 2]) + + mfe = m.private_data('my_scope') + self.assertIsInstance(mfe, dict) + mfe2 = m.private_data('another_scope') + self.assertIsInstance(mfe2, dict) + self.assertEqual(len(m._private_data), 2) + + mfe = m.b.private_data('my_scope') + self.assertIsInstance(mfe, dict) + + if __name__ == "__main__": unittest.main() From 95f005ab9b6bfa006120401d7b9f53f599a5c292 Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 15 Feb 2024 10:27:03 -0700 Subject: [PATCH 2/5] Actually putting private_data on _BlockData (whoops), adding a kind of silly test --- pyomo/core/base/block.py | 22 +++++++++++----------- pyomo/core/tests/unit/test_block.py | 5 ++++- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index d10754082bd..73ac2ebf397 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -2029,6 +2029,17 @@ def _create_objects_for_deepcopy(self, memo, component_list): comp._create_objects_for_deepcopy(memo, component_list) return _ans + @property + def _private_data(self): + if self._private_data_dict is None: + self._private_data_dict = {} + return self._private_data_dict + + def private_data(self, scope): + if scope not in self._private_data: + self._private_data[scope] = {} + return self._private_data[scope] + @ModelComponentFactory.register( "A component that contains one or more model components." @@ -2242,17 +2253,6 @@ def display(self, filename=None, ostream=None, prefix=""): for key in sorted(self): _BlockData.display(self[key], filename, ostream, prefix) - @property - def _private_data(self): - if self._private_data_dict is None: - self._private_data_dict = {} - return self._private_data_dict - - def private_data(self, scope): - if scope not in self._private_data: - self._private_data[scope] = {} - return self._private_data[scope] - class ScalarBlock(_BlockData, Block): def __init__(self, *args, **kwds): diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index 803237b1588..93333d9f764 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -3420,7 +3420,10 @@ def test_private_data(self): mfe = m.b.private_data('my_scope') self.assertIsInstance(mfe, dict) - + mfe1 = m.b.b[1].private_data('no mice here') + self.assertIsInstance(mfe1, dict) + mfe2 = m.b.b[2].private_data('no mice here') + self.assertIsInstance(mfe2, dict) if __name__ == "__main__": From cc54ae506818d2fe3dbeb728b2ff3bba2ffa973f Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 15 Feb 2024 11:50:37 -0700 Subject: [PATCH 3/5] Making _private_data the actual dict and doing away with the property --- pyomo/core/base/block.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index 73ac2ebf397..f0e0e60350f 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -550,7 +550,7 @@ def __init__(self, component): super(_BlockData, self).__setattr__('_ctypes', {}) super(_BlockData, self).__setattr__('_decl', {}) super(_BlockData, self).__setattr__('_decl_order', []) - self._private_data_dict = None + self._private_data = None def __getattr__(self, val): if val in ModelComponentFactory: @@ -2029,13 +2029,9 @@ def _create_objects_for_deepcopy(self, memo, component_list): comp._create_objects_for_deepcopy(memo, component_list) return _ans - @property - def _private_data(self): - if self._private_data_dict is None: - self._private_data_dict = {} - return self._private_data_dict - def private_data(self, scope): + if self._private_data is None: + self._private_data = {} if scope not in self._private_data: self._private_data[scope] = {} return self._private_data[scope] From 73977aa5b8f3dd501644a928d60d0a7c352491af Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 15 Feb 2024 12:18:03 -0700 Subject: [PATCH 4/5] Ensuring private_data really is private by limiting the possible keys to substrings of the caller's scope --- pyomo/core/base/block.py | 14 +++++++-- pyomo/core/tests/unit/test_block.py | 46 ++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/pyomo/core/base/block.py b/pyomo/core/base/block.py index f0e0e60350f..ea12295b7bd 100644 --- a/pyomo/core/base/block.py +++ b/pyomo/core/base/block.py @@ -29,7 +29,7 @@ import textwrap from contextlib import contextmanager -from inspect import isclass +from inspect import isclass, currentframe from itertools import filterfalse, chain from operator import itemgetter, attrgetter from io import StringIO @@ -2029,7 +2029,17 @@ def _create_objects_for_deepcopy(self, memo, component_list): comp._create_objects_for_deepcopy(memo, component_list) return _ans - def private_data(self, scope): + def private_data(self, scope=None): + mod = currentframe().f_back.f_globals['__name__'] + if scope is None: + scope = mod + elif not mod.startswith(scope): + raise ValueError( + "All keys in the 'private_data' dictionary must " + "be substrings of the caller's module name. " + "Received '%s' when calling private_data on Block " + "'%s'." % (scope, self.name) + ) if self._private_data is None: self._private_data = {} if scope not in self._private_data: diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index 93333d9f764..eb9c449af21 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -3412,18 +3412,44 @@ def test_private_data(self): m.b = Block() m.b.b = Block([1, 2]) - mfe = m.private_data('my_scope') + mfe = m.private_data() self.assertIsInstance(mfe, dict) - mfe2 = m.private_data('another_scope') - self.assertIsInstance(mfe2, dict) - self.assertEqual(len(m._private_data), 2) + self.assertEqual(len(mfe), 0) + self.assertEqual(len(m._private_data), 1) + self.assertIn('pyomo.core.tests.unit.test_block', m._private_data) + self.assertIs(mfe, m._private_data['pyomo.core.tests.unit.test_block']) - mfe = m.b.private_data('my_scope') - self.assertIsInstance(mfe, dict) - mfe1 = m.b.b[1].private_data('no mice here') - self.assertIsInstance(mfe1, dict) - mfe2 = m.b.b[2].private_data('no mice here') - self.assertIsInstance(mfe2, dict) + with self.assertRaisesRegex( + ValueError, + "All keys in the 'private_data' dictionary must " + "be substrings of the caller's module name. " + "Received 'no mice here' when calling private_data on Block " + "'b'.", + ): + mfe2 = m.b.private_data('no mice here') + + mfe3 = m.b.b[1].private_data('pyomo.core.tests') + self.assertIsInstance(mfe3, dict) + self.assertEqual(len(mfe3), 0) + self.assertIsInstance(m.b.b[1]._private_data, dict) + self.assertEqual(len(m.b.b[1]._private_data), 1) + self.assertIn('pyomo.core.tests', m.b.b[1]._private_data) + self.assertIs(mfe3, m.b.b[1]._private_data['pyomo.core.tests']) + mfe3['there are cookies'] = 'but no mice' + + mfe4 = m.b.b[1].private_data('pyomo.core.tests') + self.assertIs(mfe4, mfe3) + + # mfe2 = m.private_data('another_scope') + # self.assertIsInstance(mfe2, dict) + # self.assertEqual(len(m._private_data), 2) + + # mfe = m.b.private_data('my_scope') + # self.assertIsInstance(mfe, dict) + # mfe1 = m.b.b[1].private_data('no mice here') + # self.assertIsInstance(mfe1, dict) + # mfe2 = m.b.b[2].private_data('no mice here') + # self.assertIsInstance(mfe2, dict) if __name__ == "__main__": From 170acb83c80e6f452217ab39c590ab656d1753fc Mon Sep 17 00:00:00 2001 From: Emma Johnson Date: Thu, 15 Feb 2024 12:22:20 -0700 Subject: [PATCH 5/5] Whoops, removing comments --- pyomo/core/tests/unit/test_block.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pyomo/core/tests/unit/test_block.py b/pyomo/core/tests/unit/test_block.py index eb9c449af21..0ffdb537ac1 100644 --- a/pyomo/core/tests/unit/test_block.py +++ b/pyomo/core/tests/unit/test_block.py @@ -3440,17 +3440,6 @@ def test_private_data(self): mfe4 = m.b.b[1].private_data('pyomo.core.tests') self.assertIs(mfe4, mfe3) - # mfe2 = m.private_data('another_scope') - # self.assertIsInstance(mfe2, dict) - # self.assertEqual(len(m._private_data), 2) - - # mfe = m.b.private_data('my_scope') - # self.assertIsInstance(mfe, dict) - # mfe1 = m.b.b[1].private_data('no mice here') - # self.assertIsInstance(mfe1, dict) - # mfe2 = m.b.b[2].private_data('no mice here') - # self.assertIsInstance(mfe2, dict) - if __name__ == "__main__": unittest.main()