Skip to content

Commit aeecc27

Browse files
authored
Merge pull request #425 from ExaWorks/fix-env-type
Fix env type
2 parents c07a62d + 88bda65 commit aeecc27

File tree

6 files changed

+42
-19
lines changed

6 files changed

+42
-19
lines changed

src/psij/job_executor.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def _check_job(self, job: Job) -> JobSpec:
6565
Verifies that various aspects of the job are correctly specified. This includes precisely
6666
the following checks:
6767
* the job has a non-null specification
68-
* job.spec.environment is a Dict[str, str]
68+
* job.spec.environment is a Dict[str, [str | int]]
6969
7070
While this method makes a fair attempt at ensuring the validity of the job, it makes no
7171
such guarantees. Specifically, if an executor implementation requires checks not listed
@@ -99,9 +99,9 @@ def _check_job(self, job: Job) -> JobSpec:
9999
if not isinstance(k, str):
100100
raise TypeError('environment key "%s" is not a string (%s)'
101101
% (k, type(k).__name__))
102-
if not isinstance(v, str):
103-
raise TypeError('environment key "%s" has non-string value (%s)'
104-
% (k, type(v).__name__))
102+
if not isinstance(v, (str, int)):
103+
raise TypeError('environment value for key "%s" must be string '
104+
'or int type (%s)' % (k, type(v).__name__))
105105

106106
if job.executor is not None:
107107
raise InvalidJobException('Job is already associated with an executor')

src/psij/job_spec.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ def _to_path(arg: Union[str, pathlib.Path, None]) -> Optional[pathlib.Path]:
2121
return pathlib.Path(arg)
2222

2323

24+
def _to_env_dict(arg: Union[Dict[str, Union[str, int]], None]) -> Optional[Dict[str, str]]:
25+
if arg is None:
26+
return None
27+
ret = dict()
28+
for k, v in arg.items():
29+
if isinstance(v, int):
30+
ret[k] = str(v)
31+
else:
32+
ret[k] = v
33+
return ret
34+
35+
2436
class JobSpec(object):
2537
"""A class that describes the details of a job."""
2638

@@ -29,7 +41,8 @@ def __init__(self, executable: Optional[str] = None, arguments: Optional[List[st
2941
# sphinx fails to find the class. Using Path in the getters and setters does not
3042
# appear to trigger a problem.
3143
directory: Union[str, pathlib.Path, None] = None, name: Optional[str] = None,
32-
inherit_environment: bool = True, environment: Optional[Dict[str, str]] = None,
44+
inherit_environment: bool = True,
45+
environment: Optional[Dict[str, Union[str, int]]] = None,
3346
stdin_path: Union[str, pathlib.Path, None] = None,
3447
stdout_path: Union[str, pathlib.Path, None] = None,
3548
stderr_path: Union[str, pathlib.Path, None] = None,
@@ -125,7 +138,7 @@ def __init__(self, executable: Optional[str] = None, arguments: Optional[List[st
125138
# care of the conversion, but mypy gets confused
126139
self._directory = _to_path(directory)
127140
self.inherit_environment = inherit_environment
128-
self.environment = environment
141+
self.environment = _to_env_dict(environment)
129142
self._stdin_path = _to_path(stdin_path)
130143
self._stdout_path = _to_path(stdout_path)
131144
self._stderr_path = _to_path(stderr_path)
@@ -152,6 +165,16 @@ def name(self) -> Optional[str]:
152165
def name(self, value: Optional[str]) -> None:
153166
self._name = value
154167

168+
@property
169+
def environment(self) -> Optional[Dict[str, str]]:
170+
"""Return the environment dict."""
171+
return self._environment
172+
173+
@environment.setter
174+
def environment(self, env: Optional[Dict[str, Union[str, int]]]) -> None:
175+
"""Ensure env dict values to be string typed."""
176+
self._environment = _to_env_dict(env)
177+
155178
@property
156179
def directory(self) -> Optional[pathlib.Path]:
157180
"""The directory, on the compute side, in which the executable is to be run."""

tests/plugins1/_batch_test/test/test.mustache

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ done
4343
export PSIJ_NODEFILE
4444

4545
{{#job.spec.inherit_environment}}env \{{/job.spec.inherit_environment}}{{^job.spec.inherit_environment}}env --ignore-environment \{{/job.spec.inherit_environment}}{{#env}}
46-
{{name}}="{{value}}" \
47-
{{/env}}{{#psij.launch_command}}{{.}} {{/psij.launch_command}}
46+
{{name}}="{{value}}" \{{/env}}
47+
{{#psij.launch_command}}{{.}} {{/psij.launch_command}}
4848

49-
echo "$?" > "{{psij.script_dir}}/$PSIJ_BATCH_TEST_JOB_ID.ec"
49+
echo "$?" > "{{psij.script_dir}}/$PSIJ_BATCH_TEST_JOB_ID.ec"

tests/test_doc_examples.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ def test_job_parameters() -> None:
114114
psij.JobSpec(
115115
executable="/bin/hostname",
116116
stdout_path=output_path,
117-
environment={"FOOBAR": "BAZ"}, # custom environment has no effect here
117+
environment={"FOOBAR": "BAZ", "BUZ": 1}, # custom environment has no effect here
118118
directory=pathlib.Path(td), # CWD has no effect on result here
119119
)
120120
)

tests/test_executor.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,18 +122,19 @@ def test_env_var(execparams: ExecutorTestParams) -> None:
122122
_make_test_dir()
123123
with TemporaryDirectory(dir=Path.home() / '.psij' / 'test') as td:
124124
outp = Path(td, 'stdout.txt')
125-
job = Job(JobSpec(executable='/bin/bash', arguments=['-c', 'echo -n $TEST_VAR'],
125+
job = Job(JobSpec(executable='/bin/bash',
126+
arguments=['-c', 'env > /tmp/t; echo -n $TEST_VAR$TEST_INT'],
126127
stdout_path=outp))
127128
assert job.spec is not None
128-
job.spec.environment = {'TEST_VAR': '_y_'}
129+
job.spec.environment = {'TEST_INT': 1, 'TEST_VAR': '_y_'} # type: ignore
129130
ex = _get_executor_instance(execparams, job)
130131
ex.submit(job)
131132
status = job.wait(timeout=_get_timeout(execparams))
132133
assert_completed(job, status)
133134
f = outp.open("r")
134135
contents = f.read()
135136
f.close()
136-
assert contents == '_y_'
137+
assert contents == '_y_1'
137138

138139

139140
def test_stdin_redirect(execparams: ExecutorTestParams) -> None:

tests/test_job_spec.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,14 @@ def _test_spec(spec: JobSpec) -> None:
1212

1313

1414
def test_environment_types() -> None:
15-
with pytest.raises(TypeError):
16-
_test_spec(JobSpec(environment={'foo': 1})) # type: ignore
1715

1816
with pytest.raises(TypeError):
19-
_test_spec(JobSpec(environment={1: 'foo'})) # type: ignore
17+
_test_spec(JobSpec(executable='true', environment={1: 'foo'})) # type: ignore
2018

2119
with pytest.raises(TypeError):
22-
spec = JobSpec()
20+
spec = JobSpec(executable='true')
2321
spec.environment = {'foo': 'bar'}
24-
spec.environment['buz'] = 2 # type: ignore
22+
spec.environment['buz'] = [2] # type: ignore
2523
_test_spec(spec)
2624

2725
spec = JobSpec()
@@ -34,8 +32,9 @@ def test_environment_types() -> None:
3432
spec.environment = {'foo': 'bar'}
3533
assert spec.environment['foo'] == 'bar'
3634

37-
spec.environment = {'foo': 'biz'}
35+
spec.environment = {'foo': 'biz', 'bar': 42} # type: ignore
3836
assert spec.environment['foo'] == 'biz'
37+
assert spec.environment['bar'] == '42'
3938

4039

4140
def test_path_conversion() -> None:

0 commit comments

Comments
 (0)