Skip to content

Commit

Permalink
Merge pull request #3140 from shermanjasonaf/config-path-and-isinstan…
Browse files Browse the repository at this point in the history
…ce-check

Extend Path and Type Checking Validators of `common.config`
  • Loading branch information
mrmundt authored Feb 16, 2024
2 parents 0ed5c59 + fd81e15 commit 42bd0a7
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 1 deletion.
52 changes: 51 additions & 1 deletion pyomo/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,55 @@ def domain_name(self):
return f'InEnum[{self._domain.__name__}]'


class IsInstance(object):
"""
Domain validator for type checking.
Parameters
----------
*bases : tuple of type
Valid types.
"""

def __init__(self, *bases):
assert bases
self.baseClasses = bases

@staticmethod
def _fullname(klass):
"""
Get full name of class, including appropriate module qualifier.
"""
module_name = klass.__module__
module_qual = "" if module_name == "builtins" else f"{module_name}."
return f"{module_qual}{klass.__name__}"

def __call__(self, obj):
if isinstance(obj, self.baseClasses):
return obj
if len(self.baseClasses) > 1:
class_names = ", ".join(
f"{self._fullname(kls)!r}" for kls in self.baseClasses
)
msg = (
"Expected an instance of one of these types: "
f"{class_names}, but received value {obj!r} of type "
f"{self._fullname(type(obj))!r}"
)
else:
msg = (
f"Expected an instance of "
f"{self._fullname(self.baseClasses[0])!r}, "
f"but received value {obj!r} of type {self._fullname(type(obj))!r}"
)
raise ValueError(msg)

def domain_name(self):
return (
f"IsInstance({', '.join(self._fullname(kls) for kls in self.baseClasses)})"
)


class ListOf(object):
"""Domain validator for lists of a specified type
Expand Down Expand Up @@ -454,7 +503,7 @@ def __init__(self, basePath=None, expandPath=None):
self.expandPath = expandPath

def __call__(self, path):
path = str(path)
path = os.fsdecode(path)
_expand = self.expandPath
if _expand is None:
_expand = not Path.SuppressPathExpansion
Expand Down Expand Up @@ -709,6 +758,7 @@ def from_enum_or_string(cls, arg):
NonNegativeFloat
In
InEnum
IsInstance
ListOf
Module
Path
Expand Down
165 changes: 165 additions & 0 deletions pyomo/common/tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def yaml_load(arg):
NonPositiveFloat,
NonNegativeFloat,
In,
IsInstance,
ListOf,
Module,
Path,
Expand Down Expand Up @@ -448,12 +449,62 @@ class TestEnum(enum.Enum):
with self.assertRaisesRegex(ValueError, '.*invalid value'):
cfg.enum = 'ITEM_THREE'

def test_IsInstance(self):
c = ConfigDict()
c.declare("val", ConfigValue(None, IsInstance(int)))
c.val = 1
self.assertEqual(c.val, 1)
exc_str = (
"Expected an instance of 'int', but received value 2.4 of type 'float'"
)
with self.assertRaisesRegex(ValueError, exc_str):
c.val = 2.4

class TestClass:
def __repr__(self):
return f"{TestClass.__name__}()"

c.declare("val2", ConfigValue(None, IsInstance(TestClass)))
testinst = TestClass()
c.val2 = testinst
self.assertEqual(c.val2, testinst)
exc_str = (
r"Expected an instance of '.*\.TestClass', "
"but received value 2.4 of type 'float'"
)
with self.assertRaisesRegex(ValueError, exc_str):
c.val2 = 2.4

c.declare("val3", ConfigValue(None, IsInstance(int, TestClass)))
self.assertRegex(
c.get("val3").domain_name(), r"IsInstance\(int, .*\.TestClass\)"
)
c.val3 = 2
self.assertEqual(c.val3, 2)
exc_str = (
r"Expected an instance of one of these types: 'int', '.*\.TestClass'"
r", but received value 2.4 of type 'float'"
)
with self.assertRaisesRegex(ValueError, exc_str):
c.val3 = 2.4

def test_Path(self):
def norm(x):
if cwd[1] == ':' and x[0] == '/':
x = cwd[:2] + x
return x.replace('/', os.path.sep)

class ExamplePathLike:
def __init__(self, path_str_or_bytes):
self.path = path_str_or_bytes

def __fspath__(self):
return self.path

def __str__(self):
path_str = str(self.path)
return f"{type(self).__name__}({path_str})"

cwd = os.getcwd() + os.path.sep
c = ConfigDict()

Expand All @@ -462,12 +513,30 @@ def norm(x):
c.a = "/a/b/c"
self.assertTrue(os.path.sep in c.a)
self.assertEqual(c.a, norm('/a/b/c'))
c.a = b"/a/b/c"
self.assertTrue(os.path.sep in c.a)
self.assertEqual(c.a, norm('/a/b/c'))
c.a = ExamplePathLike("/a/b/c")
self.assertTrue(os.path.sep in c.a)
self.assertEqual(c.a, norm('/a/b/c'))
c.a = "a/b/c"
self.assertTrue(os.path.sep in c.a)
self.assertEqual(c.a, norm(cwd + 'a/b/c'))
c.a = b'a/b/c'
self.assertTrue(os.path.sep in c.a)
self.assertEqual(c.a, norm(cwd + 'a/b/c'))
c.a = ExamplePathLike('a/b/c')
self.assertTrue(os.path.sep in c.a)
self.assertEqual(c.a, norm(cwd + 'a/b/c'))
c.a = "${CWD}/a/b/c"
self.assertTrue(os.path.sep in c.a)
self.assertEqual(c.a, norm(cwd + 'a/b/c'))
c.a = b'${CWD}/a/b/c'
self.assertTrue(os.path.sep in c.a)
self.assertEqual(c.a, norm(cwd + 'a/b/c'))
c.a = ExamplePathLike('${CWD}/a/b/c')
self.assertTrue(os.path.sep in c.a)
self.assertEqual(c.a, norm(cwd + 'a/b/c'))
c.a = None
self.assertIs(c.a, None)

Expand All @@ -476,12 +545,30 @@ def norm(x):
c.b = "/a/b/c"
self.assertTrue(os.path.sep in c.b)
self.assertEqual(c.b, norm('/a/b/c'))
c.b = b"/a/b/c"
self.assertTrue(os.path.sep in c.b)
self.assertEqual(c.b, norm('/a/b/c'))
c.b = ExamplePathLike("/a/b/c")
self.assertTrue(os.path.sep in c.b)
self.assertEqual(c.b, norm('/a/b/c'))
c.b = "a/b/c"
self.assertTrue(os.path.sep in c.b)
self.assertEqual(c.b, norm(cwd + 'rel/path/a/b/c'))
c.b = b"a/b/c"
self.assertTrue(os.path.sep in c.b)
self.assertEqual(c.b, norm(cwd + 'rel/path/a/b/c'))
c.b = ExamplePathLike("a/b/c")
self.assertTrue(os.path.sep in c.b)
self.assertEqual(c.b, norm(cwd + "rel/path/a/b/c"))
c.b = "${CWD}/a/b/c"
self.assertTrue(os.path.sep in c.b)
self.assertEqual(c.b, norm(cwd + 'a/b/c'))
c.b = b"${CWD}/a/b/c"
self.assertTrue(os.path.sep in c.b)
self.assertEqual(c.b, norm(cwd + 'a/b/c'))
c.b = ExamplePathLike("${CWD}/a/b/c")
self.assertTrue(os.path.sep in c.b)
self.assertEqual(c.b, norm(cwd + 'a/b/c'))
c.b = None
self.assertIs(c.b, None)

Expand All @@ -490,12 +577,30 @@ def norm(x):
c.c = "/a/b/c"
self.assertTrue(os.path.sep in c.c)
self.assertEqual(c.c, norm('/a/b/c'))
c.c = b"/a/b/c"
self.assertTrue(os.path.sep in c.c)
self.assertEqual(c.c, norm('/a/b/c'))
c.c = ExamplePathLike("/a/b/c")
self.assertTrue(os.path.sep in c.c)
self.assertEqual(c.c, norm('/a/b/c'))
c.c = "a/b/c"
self.assertTrue(os.path.sep in c.c)
self.assertEqual(c.c, norm('/my/dir/a/b/c'))
c.c = b"a/b/c"
self.assertTrue(os.path.sep in c.c)
self.assertEqual(c.c, norm('/my/dir/a/b/c'))
c.c = ExamplePathLike("a/b/c")
self.assertTrue(os.path.sep in c.c)
self.assertEqual(c.c, norm("/my/dir/a/b/c"))
c.c = "${CWD}/a/b/c"
self.assertTrue(os.path.sep in c.c)
self.assertEqual(c.c, norm(cwd + 'a/b/c'))
c.c = b"${CWD}/a/b/c"
self.assertTrue(os.path.sep in c.c)
self.assertEqual(c.c, norm(cwd + 'a/b/c'))
c.c = ExamplePathLike("${CWD}/a/b/c")
self.assertTrue(os.path.sep in c.c)
self.assertEqual(c.c, norm(cwd + 'a/b/c'))
c.c = None
self.assertIs(c.c, None)

Expand All @@ -505,12 +610,30 @@ def norm(x):
c.d = "/a/b/c"
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm('/a/b/c'))
c.d = b"/a/b/c"
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm('/a/b/c'))
c.d = ExamplePathLike("/a/b/c")
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm('/a/b/c'))
c.d = "a/b/c"
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm(cwd + 'a/b/c'))
c.d = b"a/b/c"
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm(cwd + 'a/b/c'))
c.d = ExamplePathLike("a/b/c")
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm(cwd + 'a/b/c'))
c.d = "${CWD}/a/b/c"
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm(cwd + 'a/b/c'))
c.d = b"${CWD}/a/b/c"
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm(cwd + 'a/b/c'))
c.d = ExamplePathLike("${CWD}/a/b/c")
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm(cwd + 'a/b/c'))

c.d_base = '/my/dir'
c.d = "/a/b/c"
Expand All @@ -527,27 +650,69 @@ def norm(x):
c.d = "/a/b/c"
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm('/a/b/c'))
c.d = b"/a/b/c"
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm('/a/b/c'))
c.d = ExamplePathLike("/a/b/c")
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm('/a/b/c'))
c.d = "a/b/c"
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm(cwd + 'rel/path/a/b/c'))
c.d = b"a/b/c"
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm(cwd + 'rel/path/a/b/c'))
c.d = ExamplePathLike("a/b/c")
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm(cwd + 'rel/path/a/b/c'))
c.d = "${CWD}/a/b/c"
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm(cwd + 'a/b/c'))
c.d = b"${CWD}/a/b/c"
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm(cwd + 'a/b/c'))
c.d = ExamplePathLike("${CWD}/a/b/c")
self.assertTrue(os.path.sep in c.d)
self.assertEqual(c.d, norm(cwd + 'a/b/c'))

try:
Path.SuppressPathExpansion = True
c.d = "/a/b/c"
self.assertTrue('/' in c.d)
self.assertTrue('\\' not in c.d)
self.assertEqual(c.d, '/a/b/c')
c.d = b"/a/b/c"
self.assertTrue('/' in c.d)
self.assertTrue('\\' not in c.d)
self.assertEqual(c.d, '/a/b/c')
c.d = ExamplePathLike("/a/b/c")
self.assertTrue('/' in c.d)
self.assertTrue('\\' not in c.d)
self.assertEqual(c.d, '/a/b/c')
c.d = "a/b/c"
self.assertTrue('/' in c.d)
self.assertTrue('\\' not in c.d)
self.assertEqual(c.d, 'a/b/c')
c.d = b"a/b/c"
self.assertTrue('/' in c.d)
self.assertTrue('\\' not in c.d)
self.assertEqual(c.d, 'a/b/c')
c.d = ExamplePathLike("a/b/c")
self.assertTrue('/' in c.d)
self.assertTrue('\\' not in c.d)
self.assertEqual(c.d, 'a/b/c')
c.d = "${CWD}/a/b/c"
self.assertTrue('/' in c.d)
self.assertTrue('\\' not in c.d)
self.assertEqual(c.d, "${CWD}/a/b/c")
c.d = b"${CWD}/a/b/c"
self.assertTrue('/' in c.d)
self.assertTrue('\\' not in c.d)
self.assertEqual(c.d, "${CWD}/a/b/c")
c.d = ExamplePathLike("${CWD}/a/b/c")
self.assertTrue('/' in c.d)
self.assertTrue('\\' not in c.d)
self.assertEqual(c.d, "${CWD}/a/b/c")
finally:
Path.SuppressPathExpansion = False

Expand Down

0 comments on commit 42bd0a7

Please sign in to comment.