Skip to content

Commit 9618ab0

Browse files
authored
Merge pull request #169 from yucongalicechen/wavelength-config
feat: config workflow for wavelength / anode type
2 parents bd71eda + b19d44a commit 9618ab0

File tree

4 files changed

+205
-7
lines changed

4 files changed

+205
-7
lines changed

Diff for: news/wavelength-config.rst

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
**Added:**
2+
3+
* Functionality to read wavelength and anode type directly from a diffpy configuration file.
4+
5+
**Changed:**
6+
7+
* <news item>
8+
9+
**Deprecated:**
10+
11+
* <news item>
12+
13+
**Removed:**
14+
15+
* <news item>
16+
17+
**Fixed:**
18+
19+
* <news item>
20+
21+
**Security:**
22+
23+
* <news item>

Diff for: src/diffpy/labpdfproc/tools.py

+51-4
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
XQUANTITIES,
88
)
99
from diffpy.utils.tools import (
10+
_load_config,
1011
check_and_build_global_config,
1112
compute_mud,
1213
get_package_info,
@@ -159,6 +160,52 @@ def set_input_lists(args):
159160
return args
160161

161162

163+
def load_wavelength_from_config_file(args):
164+
"""Load wavelength and anode type from config files.
165+
166+
It prioritizes values in the following order:
167+
1. cli inputs, 2. local config file, 3. global config file.
168+
169+
Parameters
170+
----------
171+
args : argparse.Namespace
172+
The arguments from the parser.
173+
174+
Returns
175+
-------
176+
args : argparse.Namespace
177+
The updated arguments with the updated wavelength and anode type.
178+
"""
179+
global_config = _load_config(Path().home() / "diffpyconfig.json")
180+
local_config = _load_config(Path().cwd() / "diffpyconfig.json")
181+
local_has_data = local_config and (
182+
"wavelength" in local_config or "anode_type" in local_config
183+
)
184+
global_has_data = global_config and (
185+
"wavelength" in global_config or "anode_type" in global_config
186+
)
187+
if not local_has_data and not global_has_data:
188+
print(
189+
"No configuration file was found containing information "
190+
"about the wavelength or anode type. \n"
191+
"You can add the wavelength or anode type "
192+
"to a configuration file on the current computer "
193+
"and it will be automatically associated with "
194+
"subsequent diffpy data by default. \n"
195+
"You will only have to do that once. \n"
196+
"For more information, please refer to www.diffpy.org/"
197+
"diffpy.labpdfproc/examples/toolsexample.html"
198+
)
199+
200+
if args.wavelength or args.anode_type:
201+
return args
202+
config = local_config if local_has_data else global_config
203+
if config:
204+
args.wavelength = args.wavelength or config.get("wavelength")
205+
args.anode_type = args.anode_type or config.get("anode_type")
206+
return args
207+
208+
162209
def set_wavelength(args):
163210
"""Set the wavelength based on the given anode_type or wavelength.
164211
@@ -174,7 +221,7 @@ def set_wavelength(args):
174221
------
175222
ValueError
176223
Raised if:
177-
(1) neither wavelength or anode type is provided,
224+
(1) neither wavelength or anode type is provided
178225
and xtype is not the two-theta grid,
179226
(2) both are provided,
180227
(3) anode_type is not one of the known sources,
@@ -185,7 +232,7 @@ def set_wavelength(args):
185232
args : argparse.Namespace
186233
The updated arguments with the wavelength.
187234
"""
188-
# first load values from config file
235+
args = load_wavelength_from_config_file(args)
189236
if args.wavelength is None and args.anode_type is None:
190237
if args.xtype not in ANGLEQUANTITIES:
191238
raise ValueError(
@@ -209,15 +256,15 @@ def set_wavelength(args):
209256
)
210257
if matched_anode_type is None:
211258
raise ValueError(
212-
f"Anode type not recognized. "
259+
f"Anode type '{args.anode_type}' not recognized. "
213260
f"Please rerun specifying an anode_type "
214261
f"from {*known_sources, }."
215262
)
216263
args.anode_type = matched_anode_type
217264
args.wavelength = WAVELENGTHS[args.anode_type]
218265
elif args.wavelength is not None and args.wavelength <= 0:
219266
raise ValueError(
220-
"No valid wavelength. "
267+
f"Wavelength = {args.wavelength} is not valid. "
221268
"Please rerun specifying a known anode_type "
222269
"or a positive wavelength."
223270
)

Diff for: tests/conftest.py

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ def user_filesystem(tmp_path):
6060
f.write(f"{str(input_dir.resolve() / 'good_data.txt')}\n")
6161

6262
home_config_data = {
63+
"wavelength": 0.3,
6364
"owner_name": "home_username",
6465
"owner_email": "[email protected]",
6566
"owner_orcid": "home_orcid",

Diff for: tests/test_tools.py

+130-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import json
12
import os
23
import re
34
from pathlib import Path
@@ -11,6 +12,7 @@
1112
load_package_info,
1213
load_user_info,
1314
load_user_metadata,
15+
load_wavelength_from_config_file,
1416
preprocessing_args,
1517
set_input_lists,
1618
set_mud,
@@ -201,6 +203,131 @@ def test_set_output_directory_bad(user_filesystem):
201203
assert not Path(actual_args.output_directory).is_dir()
202204

203205

206+
@pytest.mark.parametrize(
207+
"inputs, expected",
208+
[
209+
# Test with only a home config file (no local config),
210+
# expect to return values directly from args
211+
# if either wavelength or anode type is specified,
212+
# otherwise update args with values from the home config file
213+
# (wavelength=0.3, no anode type).
214+
# This test only checks loading behavior,
215+
# not value validation (which is handled by `set_wavelength`).
216+
# C1: no args, expect to update arg values from home config
217+
([], {"wavelength": 0.3, "anode_type": None}),
218+
# C2: wavelength provided, expect to return args unchanged
219+
(["--wavelength", "0.25"], {"wavelength": 0.25, "anode_type": None}),
220+
# C3: anode type provided, expect to return args unchanged
221+
(["--anode-type", "Mo"], {"wavelength": None, "anode_type": "Mo"}),
222+
# C4: both wavelength and anode type provided,
223+
# expect to return args unchanged
224+
(
225+
["--wavelength", "0.7", "--anode-type", "Mo"],
226+
{"wavelength": 0.7, "anode_type": "Mo"},
227+
),
228+
],
229+
)
230+
def test_load_wavelength_from_config_file_with_home_conf_file(
231+
mocker, user_filesystem, inputs, expected
232+
):
233+
cwd = Path(user_filesystem)
234+
home_dir = cwd / "home_dir"
235+
mocker.patch("pathlib.Path.home", lambda _: home_dir)
236+
os.chdir(cwd)
237+
238+
cli_inputs = ["data.xy", "--mud", "2.5"] + inputs
239+
actual_args = get_args(cli_inputs)
240+
actual_args = load_wavelength_from_config_file(actual_args)
241+
assert actual_args.wavelength == expected["wavelength"]
242+
assert actual_args.anode_type == expected["anode_type"]
243+
244+
245+
@pytest.mark.parametrize(
246+
"inputs, expected",
247+
[
248+
# Test when a local config file exists,
249+
# expect to return values directly from args
250+
# if either wavelength or anode type is specified,
251+
# otherwise update args with values from the local config file
252+
# (wavelength=0.6, no anode type).
253+
# Results should be the same whether if the home config exists.
254+
# This test only checks loading behavior,
255+
# not value validation (which is handled by `set_wavelength`).
256+
# C1: no args, expect to update arg values from local config
257+
([], {"wavelength": 0.6, "anode_type": None}),
258+
# C2: wavelength provided, expect to return args unchanged
259+
(["--wavelength", "0.25"], {"wavelength": 0.25, "anode_type": None}),
260+
# C3: anode type provided, expect to return args unchanged
261+
(["--anode-type", "Mo"], {"wavelength": None, "anode_type": "Mo"}),
262+
# C4: both wavelength and anode type provided,
263+
# expect to return args unchanged
264+
(
265+
["--wavelength", "0.7", "--anode-type", "Mo"],
266+
{"wavelength": 0.7, "anode_type": "Mo"},
267+
),
268+
],
269+
)
270+
def test_load_wavelength_from_config_file_with_local_conf_file(
271+
mocker, user_filesystem, inputs, expected
272+
):
273+
cwd = Path(user_filesystem)
274+
home_dir = cwd / "home_dir"
275+
mocker.patch("pathlib.Path.home", lambda _: home_dir)
276+
os.chdir(cwd)
277+
local_config_data = {"wavelength": 0.6}
278+
with open(cwd / "diffpyconfig.json", "w") as f:
279+
json.dump(local_config_data, f)
280+
281+
cli_inputs = ["data.xy", "--mud", "2.5"] + inputs
282+
actual_args = get_args(cli_inputs)
283+
actual_args = load_wavelength_from_config_file(actual_args)
284+
assert actual_args.wavelength == expected["wavelength"]
285+
assert actual_args.anode_type == expected["anode_type"]
286+
287+
# remove home config file, expect the same results
288+
confile = home_dir / "diffpyconfig.json"
289+
os.remove(confile)
290+
assert actual_args.wavelength == expected["wavelength"]
291+
assert actual_args.anode_type == expected["anode_type"]
292+
293+
294+
@pytest.mark.parametrize(
295+
"inputs, expected",
296+
[
297+
# Test when no config files exist,
298+
# expect to return args without modification.
299+
# This test only checks loading behavior,
300+
# not value validation (which is handled by `set_wavelength`).
301+
# C1: no args
302+
([], {"wavelength": None, "anode_type": None}),
303+
# C1: wavelength provided
304+
(["--wavelength", "0.25"], {"wavelength": 0.25, "anode_type": None}),
305+
# C2: anode type provided
306+
(["--anode-type", "Mo"], {"wavelength": None, "anode_type": "Mo"}),
307+
# C4: both wavelength and anode type provided
308+
(
309+
["--wavelength", "0.7", "--anode-type", "Mo"],
310+
{"wavelength": 0.7, "anode_type": "Mo"},
311+
),
312+
],
313+
)
314+
def test_load_wavelength_from_config_file_without_conf_files(
315+
mocker, user_filesystem, inputs, expected
316+
):
317+
cwd = Path(user_filesystem)
318+
home_dir = cwd / "home_dir"
319+
mocker.patch("pathlib.Path.home", lambda _: home_dir)
320+
os.chdir(cwd)
321+
confile = home_dir / "diffpyconfig.json"
322+
os.remove(confile)
323+
324+
cli_inputs = ["data.xy", "--mud", "2.5"] + inputs
325+
actual_args = get_args(cli_inputs)
326+
actual_args = load_wavelength_from_config_file(actual_args)
327+
assert actual_args.wavelength == expected["wavelength"]
328+
assert actual_args.anode_type == expected["anode_type"]
329+
330+
204331
@pytest.mark.parametrize(
205332
"inputs, expected",
206333
[
@@ -279,13 +406,13 @@ def test_set_wavelength(inputs, expected):
279406
( # C3: invalid anode type
280407
# expect error asking to specify a valid anode type
281408
["--anode-type", "invalid"],
282-
f"Anode type not recognized. "
409+
f"Anode type 'invalid' not recognized. "
283410
f"Please rerun specifying an anode_type from {*known_sources, }.",
284411
),
285412
( # C4: invalid wavelength
286413
# expect error asking to specify a valid wavelength or anode type
287-
["--wavelength", "0"],
288-
"No valid wavelength. "
414+
["--wavelength", "-0.2"],
415+
"Wavelength = -0.2 is not valid. "
289416
"Please rerun specifying a known anode_type "
290417
"or a positive wavelength.",
291418
),

0 commit comments

Comments
 (0)