Skip to content

Commit a1ca685

Browse files
committed
Variables cleanup: PathVariable
Part 4 of a series, updating the PathVariable implementation, tests and docstrings. Signed-off-by: Mats Wichmann <[email protected]>
1 parent bcf7158 commit a1ca685

File tree

3 files changed

+112
-135
lines changed

3 files changed

+112
-135
lines changed

SCons/Variables/PathVariable.py

Lines changed: 52 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@
2626
To be used whenever a user-specified path override setting should be allowed.
2727
2828
Arguments to PathVariable are:
29-
* *key* - name of this option on the command line (e.g. "prefix")
30-
* *help* - help string for option
31-
* *default* - default value for this option
32-
* *validator* - [optional] validator for option value. Predefined are:
29+
* *key* - name of this variable on the command line (e.g. "prefix")
30+
* *help* - help string for variable
31+
* *default* - default value for this variable
32+
* *validator* - [optional] validator for variable value. Predefined are:
3333
3434
* *PathAccept* - accepts any path setting; no validation
3535
* *PathIsDir* - path must be an existing directory
@@ -40,8 +40,8 @@
4040
The *validator* is a function that is called and which should return
4141
True or False to indicate if the path is valid. The arguments
4242
to the validator function are: (*key*, *val*, *env*). *key* is the
43-
name of the option, *val* is the path specified for the option,
44-
and *env* is the environment to which the Options have been added.
43+
name of the variable, *val* is the path specified for the variable,
44+
and *env* is the environment to which the Variables have been added.
4545
4646
Usage example::
4747
@@ -74,81 +74,93 @@
7474

7575
import os
7676
import os.path
77-
from typing import Tuple, Callable
77+
from typing import Callable, Optional, Tuple
7878

7979
import SCons.Errors
80+
import SCons.Util
8081

8182
__all__ = ['PathVariable',]
8283

8384
class _PathVariableClass:
85+
"""Class implementing path variables.
86+
87+
This class exists mainly to expose the validators without code having
88+
to import the names: they will appear as methods of ``PathVariable``,
89+
a statically created instance of this class, which is placed in
90+
the SConscript namespace.
91+
92+
Instances are callable to produce a suitable variable tuple.
93+
"""
8494

8595
@staticmethod
8696
def PathAccept(key, val, env) -> None:
87-
"""Accepts any path, no checking done."""
88-
pass
97+
"""Validate path with no checking."""
98+
return
8999

90100
@staticmethod
91101
def PathIsDir(key, val, env) -> None:
92-
"""Validator to check if Path is a directory."""
93-
if not os.path.isdir(val):
94-
if os.path.isfile(val):
95-
m = 'Directory path for option %s is a file: %s'
96-
else:
97-
m = 'Directory path for option %s does not exist: %s'
98-
raise SCons.Errors.UserError(m % (key, val))
102+
"""Validate path is a directory."""
103+
if os.path.isdir(val):
104+
return
105+
if os.path.isfile(val):
106+
msg = f'Directory path for variable {key!r} is a file: {val}'
107+
else:
108+
msg = f'Directory path for variable {key!r} does not exist: {val}'
109+
raise SCons.Errors.UserError(msg)
99110

100111
@staticmethod
101112
def PathIsDirCreate(key, val, env) -> None:
102-
"""Validator to check if Path is a directory,
103-
creating it if it does not exist."""
113+
"""Validate path is a directory, creating if needed."""
114+
if os.path.isdir(val):
115+
return
104116
try:
105117
os.makedirs(val, exist_ok=True)
106-
except FileExistsError:
107-
m = 'Path for option %s is a file, not a directory: %s'
108-
raise SCons.Errors.UserError(m % (key, val))
109-
except PermissionError:
110-
m = 'Path for option %s could not be created: %s'
111-
raise SCons.Errors.UserError(m % (key, val))
112-
except OSError:
113-
m = 'Path for option %s could not be created: %s'
114-
raise SCons.Errors.UserError(m % (key, val))
118+
except FileExistsError as exc:
119+
msg = f'Path for variable {key!r} is a file, not a directory: {val}'
120+
raise SCons.Errors.UserError(msg) from exc
121+
except (PermissionError, OSError) as exc:
122+
msg = f'Path for variable {key!r} could not be created: {val}'
123+
raise SCons.Errors.UserError(msg) from exc
115124

116125
@staticmethod
117126
def PathIsFile(key, val, env) -> None:
118-
"""Validator to check if Path is a file"""
127+
"""Validate path is a file."""
119128
if not os.path.isfile(val):
120129
if os.path.isdir(val):
121-
m = 'File path for option %s is a directory: %s'
130+
msg = f'File path for variable {key!r} is a directory: {val}'
122131
else:
123-
m = 'File path for option %s does not exist: %s'
124-
raise SCons.Errors.UserError(m % (key, val))
132+
msg = f'File path for variable {key!r} does not exist: {val}'
133+
raise SCons.Errors.UserError(msg)
125134

126135
@staticmethod
127136
def PathExists(key, val, env) -> None:
128-
"""Validator to check if Path exists"""
137+
"""Validate path exists."""
129138
if not os.path.exists(val):
130-
m = 'Path for option %s does not exist: %s'
131-
raise SCons.Errors.UserError(m % (key, val))
139+
msg = f'Path for variable {key!r} does not exist: {val}'
140+
raise SCons.Errors.UserError(msg)
132141

133-
def __call__(self, key, help, default, validator=None) -> Tuple[str, str, str, Callable, None]:
142+
# lint: W0622: Redefining built-in 'help' (redefined-builtin)
143+
def __call__(
144+
self, key, help: str, default, validator: Optional[Callable] = None
145+
) -> Tuple[str, str, str, Callable, None]:
134146
"""Return a tuple describing a path list SCons Variable.
135147
136-
The input parameters describe a 'path list' option. Returns
148+
The input parameters describe a 'path list' variable. Returns
137149
a tuple with the correct converter and validator appended. The
138150
result is usable for input to :meth:`Add`.
139151
140-
The *default* option specifies the default path to use if the
141-
user does not specify an override with this option.
152+
The *default* parameter specifies the default path to use if the
153+
user does not specify an override with this variable.
142154
143155
*validator* is a validator, see this file for examples
144156
"""
145157
if validator is None:
146158
validator = self.PathExists
147159

148160
if SCons.Util.is_List(key) or SCons.Util.is_Tuple(key):
149-
helpmsg = '%s ( /path/to/%s )' % (help, key[0])
161+
helpmsg = f'{help} ( /path/to/{key[0]} )'
150162
else:
151-
helpmsg = '%s ( /path/to/%s )' % (help, key)
163+
helpmsg = f'{help} ( /path/to/{key} )'
152164
return (key, helpmsg, default, validator, None)
153165

154166

SCons/Variables/PathVariableTests.py

Lines changed: 45 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,19 @@
2828
import SCons.Variables
2929

3030
import TestCmd
31+
from TestCmd import IS_WINDOWS
3132

3233
class PathVariableTestCase(unittest.TestCase):
3334
def test_PathVariable(self) -> None:
3435
"""Test PathVariable creation"""
3536
opts = SCons.Variables.Variables()
3637
opts.Add(SCons.Variables.PathVariable('test',
37-
'test option help',
38+
'test build variable help',
3839
'/default/path'))
3940

4041
o = opts.options[0]
4142
assert o.key == 'test', o.key
42-
assert o.help == 'test option help ( /path/to/test )', repr(o.help)
43+
assert o.help == 'test build variable help ( /path/to/test )', repr(o.help)
4344
assert o.default == '/default/path', o.default
4445
assert o.validator is not None, o.validator
4546
assert o.converter is None, o.converter
@@ -48,30 +49,27 @@ def test_PathExists(self):
4849
"""Test the PathExists validator"""
4950
opts = SCons.Variables.Variables()
5051
opts.Add(SCons.Variables.PathVariable('test',
51-
'test option help',
52+
'test build variable help',
5253
'/default/path',
5354
SCons.Variables.PathVariable.PathExists))
5455

5556
test = TestCmd.TestCmd(workdir='')
5657
test.write('exists', 'exists\n')
5758

5859
o = opts.options[0]
59-
6060
o.validator('X', test.workpath('exists'), {})
6161

6262
dne = test.workpath('does_not_exist')
63-
try:
63+
with self.assertRaises(SCons.Errors.UserError) as cm:
6464
o.validator('X', dne, {})
65-
except SCons.Errors.UserError as e:
66-
assert str(e) == 'Path for option X does not exist: %s' % dne, e
67-
except:
68-
raise Exception("did not catch expected UserError")
65+
e = cm.exception
66+
self.assertEqual(str(e), f"Path for variable 'X' does not exist: {dne}")
6967

7068
def test_PathIsDir(self):
7169
"""Test the PathIsDir validator"""
7270
opts = SCons.Variables.Variables()
7371
opts.Add(SCons.Variables.PathVariable('test',
74-
'test option help',
72+
'test build variable help',
7573
'/default/path',
7674
SCons.Variables.PathVariable.PathIsDir))
7775

@@ -80,30 +78,25 @@ def test_PathIsDir(self):
8078
test.write('file', "file\n")
8179

8280
o = opts.options[0]
83-
8481
o.validator('X', test.workpath('dir'), {})
8582

8683
f = test.workpath('file')
87-
try:
84+
with self.assertRaises(SCons.Errors.UserError) as cm:
8885
o.validator('X', f, {})
89-
except SCons.Errors.UserError as e:
90-
assert str(e) == 'Directory path for option X is a file: %s' % f, e
91-
except:
92-
raise Exception("did not catch expected UserError")
86+
e = cm.exception
87+
self.assertEqual(str(e), f"Directory path for variable 'X' is a file: {f}")
9388

9489
dne = test.workpath('does_not_exist')
95-
try:
90+
with self.assertRaises(SCons.Errors.UserError) as cm:
9691
o.validator('X', dne, {})
97-
except SCons.Errors.UserError as e:
98-
assert str(e) == 'Directory path for option X does not exist: %s' % dne, e
99-
except Exception as e:
100-
raise Exception("did not catch expected UserError") from e
92+
e = cm.exception
93+
self.assertEqual(str(e), f"Directory path for variable 'X' does not exist: {dne}")
10194

10295
def test_PathIsDirCreate(self):
10396
"""Test the PathIsDirCreate validator"""
10497
opts = SCons.Variables.Variables()
10598
opts.Add(SCons.Variables.PathVariable('test',
106-
'test option help',
99+
'test build variable help',
107100
'/default/path',
108101
SCons.Variables.PathVariable.PathIsDirCreate))
109102

@@ -117,26 +110,26 @@ def test_PathIsDirCreate(self):
117110
assert os.path.isdir(d)
118111

119112
f = test.workpath('file')
120-
try:
113+
with self.assertRaises(SCons.Errors.UserError) as cm:
121114
o.validator('X', f, {})
122-
except SCons.Errors.UserError as e:
123-
assert str(e) == 'Path for option X is a file, not a directory: %s' % f, e
124-
except Exception as e:
125-
raise Exception("did not catch expected UserError") from e
115+
e = cm.exception
116+
self.assertEqual(str(e), f"Path for variable 'X' is a file, not a directory: {f}")
126117

127-
f = '/yyy/zzz' # this not exists and should fail to create
128-
try:
118+
# pick a directory path that can't be mkdir'd
119+
if IS_WINDOWS:
120+
f = r'\\noserver\noshare\yyy\zzz'
121+
else:
122+
f = '/yyy/zzz'
123+
with self.assertRaises(SCons.Errors.UserError) as cm:
129124
o.validator('X', f, {})
130-
except SCons.Errors.UserError as e:
131-
assert str(e) == 'Path for option X could not be created: %s' % f, e
132-
except Exception as e:
133-
raise Exception("did not catch expected UserError") from e
125+
e = cm.exception
126+
self.assertEqual(str(e), f"Path for variable 'X' could not be created: {f}")
134127

135128
def test_PathIsFile(self):
136129
"""Test the PathIsFile validator"""
137130
opts = SCons.Variables.Variables()
138131
opts.Add(SCons.Variables.PathVariable('test',
139-
'test option help',
132+
'test build variable help',
140133
'/default/path',
141134
SCons.Variables.PathVariable.PathIsFile))
142135

@@ -145,30 +138,25 @@ def test_PathIsFile(self):
145138
test.write('file', "file\n")
146139

147140
o = opts.options[0]
148-
149141
o.validator('X', test.workpath('file'), {})
150142

151143
d = test.workpath('d')
152-
try:
144+
with self.assertRaises(SCons.Errors.UserError) as cm:
153145
o.validator('X', d, {})
154-
except SCons.Errors.UserError as e:
155-
assert str(e) == 'File path for option X does not exist: %s' % d, e
156-
except:
157-
raise Exception("did not catch expected UserError")
146+
e = cm.exception
147+
self.assertEqual(str(e), f"File path for variable 'X' does not exist: {d}")
158148

159149
dne = test.workpath('does_not_exist')
160-
try:
150+
with self.assertRaises(SCons.Errors.UserError) as cm:
161151
o.validator('X', dne, {})
162-
except SCons.Errors.UserError as e:
163-
assert str(e) == 'File path for option X does not exist: %s' % dne, e
164-
except:
165-
raise Exception("did not catch expected UserError")
152+
e = cm.exception
153+
self.assertEqual(str(e), f"File path for variable 'X' does not exist: {dne}")
166154

167155
def test_PathAccept(self) -> None:
168156
"""Test the PathAccept validator"""
169157
opts = SCons.Variables.Variables()
170158
opts.Add(SCons.Variables.PathVariable('test',
171-
'test option help',
159+
'test build variable help',
172160
'/default/path',
173161
SCons.Variables.PathVariable.PathAccept))
174162

@@ -177,7 +165,6 @@ def test_PathAccept(self) -> None:
177165
test.write('file', "file\n")
178166

179167
o = opts.options[0]
180-
181168
o.validator('X', test.workpath('file'), {})
182169

183170
d = test.workpath('d')
@@ -190,44 +177,37 @@ def test_validator(self):
190177
"""Test the PathVariable validator argument"""
191178
opts = SCons.Variables.Variables()
192179
opts.Add(SCons.Variables.PathVariable('test',
193-
'test option help',
180+
'test variable help',
194181
'/default/path'))
195182

196183
test = TestCmd.TestCmd(workdir='')
197184
test.write('exists', 'exists\n')
198185

199186
o = opts.options[0]
200-
201187
o.validator('X', test.workpath('exists'), {})
202188

203189
dne = test.workpath('does_not_exist')
204-
try:
190+
with self.assertRaises(SCons.Errors.UserError) as cm:
205191
o.validator('X', dne, {})
206-
except SCons.Errors.UserError as e:
207-
expect = 'Path for option X does not exist: %s' % dne
208-
assert str(e) == expect, e
209-
else:
210-
raise Exception("did not catch expected UserError")
192+
e = cm.exception
193+
self.assertEqual(str(e), f"Path for variable 'X' does not exist: {dne}")
194+
195+
class ValidatorError(Exception):
196+
pass
211197

212198
def my_validator(key, val, env):
213-
raise Exception("my_validator() got called for %s, %s!" % (key, val))
199+
raise ValidatorError(f"my_validator() got called for {key!r}, {val}!")
214200

215201
opts = SCons.Variables.Variables()
216202
opts.Add(SCons.Variables.PathVariable('test2',
217203
'more help',
218204
'/default/path/again',
219205
my_validator))
220-
221206
o = opts.options[0]
222-
223-
try:
207+
with self.assertRaises(ValidatorError) as cm:
224208
o.validator('Y', 'value', {})
225-
except Exception as e:
226-
assert str(e) == 'my_validator() got called for Y, value!', e
227-
else:
228-
raise Exception("did not catch expected exception from my_validator()")
229-
230-
209+
e = cm.exception
210+
self.assertEqual(str(e), f"my_validator() got called for 'Y', value!")
231211

232212

233213
if __name__ == "__main__":

0 commit comments

Comments
 (0)