From 411ba5938864f679c1640a98fe8233deaea85b67 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sat, 22 Feb 2025 22:23:48 -0500 Subject: [PATCH 1/6] initial commit --- src/diffpy/labpdfproc/tools.py | 30 +++++++++++++++++++++++++++++- tests/conftest.py | 1 + tests/test_tools.py | 23 +++++++++++++++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/diffpy/labpdfproc/tools.py b/src/diffpy/labpdfproc/tools.py index 3850673..75b34ff 100644 --- a/src/diffpy/labpdfproc/tools.py +++ b/src/diffpy/labpdfproc/tools.py @@ -7,6 +7,7 @@ XQUANTITIES, ) from diffpy.utils.tools import ( + _load_config, check_and_build_global_config, compute_mud, get_package_info, @@ -159,6 +160,33 @@ def set_input_lists(args): return args +def _load_wavelength_from_config_file(args): + """Load wavelength and anode type from config files. + It takes cli inputs first, and local config, and then global config. + + Parameters + ---------- + args : argparse.Namespace + The arguments from the parser. + + Returns + ------- + args : argparse.Namespace + The updated arguments with the updated wavelength and anode type. + """ + if args.wavelength or args.anode_type: + return args + global_config = _load_config(Path().home() / "diffpyconfig.json") + local_config = _load_config(Path().cwd() / "diffpyconfig.json") + if local_config: + args.wavelength = local_config.get("wavelength") + args.anode_type = local_config.get("anode_type") + elif global_config: + args.wavelength = global_config.get("wavelength") + args.anode_type = global_config.get("anode_type") + return args + + def set_wavelength(args): """Set the wavelength based on the given anode_type or wavelength. @@ -185,7 +213,7 @@ def set_wavelength(args): args : argparse.Namespace The updated arguments with the wavelength. """ - # first load values from config file + args = _load_wavelength_from_config_file(args) if args.wavelength is None and args.anode_type is None: if args.xtype not in ANGLEQUANTITIES: raise ValueError( diff --git a/tests/conftest.py b/tests/conftest.py index 2dcb4a5..18055f9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -60,6 +60,7 @@ def user_filesystem(tmp_path): f.write(f"{str(input_dir.resolve() / 'good_data.txt')}\n") home_config_data = { + "wavelength": 0.25, "owner_name": "home_username", "owner_email": "home@email.com", "owner_orcid": "home_orcid", diff --git a/tests/test_tools.py b/tests/test_tools.py index e6f2269..0e2a8fa 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -6,6 +6,7 @@ from diffpy.labpdfproc.labpdfprocapp import get_args from diffpy.labpdfproc.tools import ( + _load_wavelength_from_config_file, known_sources, load_metadata, load_package_info, @@ -195,6 +196,28 @@ def test_set_output_directory_bad(user_filesystem): assert not Path(actual_args.output_directory).is_dir() +def test_load_wavelength_from_config_file_with_home_conf_file(): + # C1: args provided, return args + # C2: no args, local config exists, return local config + # C3: no args or local config file, return global config + return True + + +def test_load_wavelength_from_config_file_with_local_conf_file(): + # C1: args provided, return args + # C2: no args, return local config file + # remove global config file to test again + return True + + +def test_load_wavelength_from_config_file_without_conf_files(user_filesystem): + # C1: args provided, return args + cli_inputs = ["2.5", "data.xy", "-w", "0.25"] + actual_args = get_args(cli_inputs) + actual_args = _load_wavelength_from_config_file(actual_args) + assert actual_args.wavelength == 0.25 + + @pytest.mark.parametrize( "inputs, expected", [ From bc118ea36092ad303b483ce6d0062db5c0a24937 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 24 Feb 2025 18:19:13 -0500 Subject: [PATCH 2/6] feat: add tests --- tests/conftest.py | 2 +- tests/test_tools.py | 122 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 109 insertions(+), 15 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 18055f9..63d4646 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -60,7 +60,7 @@ def user_filesystem(tmp_path): f.write(f"{str(input_dir.resolve() / 'good_data.txt')}\n") home_config_data = { - "wavelength": 0.25, + "wavelength": 0.3, "owner_name": "home_username", "owner_email": "home@email.com", "owner_orcid": "home_orcid", diff --git a/tests/test_tools.py b/tests/test_tools.py index 0e2a8fa..ac5cfa5 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -1,3 +1,4 @@ +import json import os import re from pathlib import Path @@ -196,26 +197,119 @@ def test_set_output_directory_bad(user_filesystem): assert not Path(actual_args.output_directory).is_dir() -def test_load_wavelength_from_config_file_with_home_conf_file(): - # C1: args provided, return args - # C2: no args, local config exists, return local config - # C3: no args or local config file, return global config - return True +@pytest.mark.parametrize( + "inputs, expected", + [ + # Test when only a home config file exists (no local config file), + # expect to return args if wavelength or anode type is specified, + # otherwise update args with values from the home config file. + # C1: no args, expect to update arg values from home config + ([""], {"wavelength": 0.3, "anode_type": None}), + # C2: wavelength provided, expect to return args unchanged + (["--wavelength", "0.25"], {"wavelength": 0.25, "anode_type": None}), + # C3: anode type provided, expect to return args unchanged + (["--anode-type", "Mo"], {"wavelength": None, "anode_type": "Mo"}), + # C4: both wavelength and anode type provided, + # expect to return args unchanged + ( + ["--wavelength", "0.7", "--anode-type", "Mo"], + {"wavelength": 0.7, "anode_type": "Mo"}, + ), + ], +) +def test_load_wavelength_from_config_file_with_home_conf_file( + mocker, user_filesystem, inputs, expected +): + cwd = Path(user_filesystem) + home_dir = cwd / "home_dir" + mocker.patch("pathlib.Path.home", lambda _: home_dir) + os.chdir(cwd) + + cli_inputs = ["2.5", "data.xy"] + inputs + actual_args = get_args(cli_inputs) + actual_args = _load_wavelength_from_config_file(actual_args) + assert actual_args.wavelength == expected["wavelength"] + assert actual_args.anode_type == expected["anode_type"] + + +@pytest.mark.parametrize( + "inputs, expected", + [ + # Test when a local config file exists, + # expect to return args if wavelength or anode type is specified, + # otherwise update args with values from the home config file. + # Results should be the same whether if the home config exists. + # C1: no args, expect to update arg values from local config + ([""], {"wavelength": 0.6, "anode_type": None}), + # C2: wavelength provided, expect to return args unchanged + (["--wavelength", "0.25"], {"wavelength": 0.25, "anode_type": None}), + # C3: anode type provided, expect to return args unchanged + (["--anode-type", "Mo"], {"wavelength": None, "anode_type": "Mo"}), + # C4: both wavelength and anode type provided, + # expect to return args unchanged + ( + ["--wavelength", "0.7", "--anode-type", "Mo"], + {"wavelength": 0.7, "anode_type": "Mo"}, + ), + ], +) +def test_load_wavelength_from_config_file_with_local_conf_file( + mocker, user_filesystem, inputs, expected +): + cwd = Path(user_filesystem) + home_dir = cwd / "home_dir" + mocker.patch("pathlib.Path.home", lambda _: home_dir) + os.chdir(cwd) + local_config_data = {"wavelength": 0.6} + with open(cwd / "diffpyconfig.json", "w") as f: + json.dump(local_config_data, f) + cli_inputs = ["2.5", "data.xy"] + inputs + actual_args = get_args(cli_inputs) + actual_args = _load_wavelength_from_config_file(actual_args) + assert actual_args.wavelength == expected["wavelength"] + assert actual_args.anode_type == expected["anode_type"] -def test_load_wavelength_from_config_file_with_local_conf_file(): - # C1: args provided, return args - # C2: no args, return local config file - # remove global config file to test again - return True + # remove home config file, expect the same results + confile = home_dir / "diffpyconfig.json" + os.remove(confile) + assert actual_args.wavelength == expected["wavelength"] + assert actual_args.anode_type == expected["anode_type"] -def test_load_wavelength_from_config_file_without_conf_files(user_filesystem): - # C1: args provided, return args - cli_inputs = ["2.5", "data.xy", "-w", "0.25"] +@pytest.mark.parametrize( + "inputs, expected", + [ + # Test when no config files exist, + # expect to return args without modification. + # C1: no args + ([""], {"wavelength": None, "anode_type": None}), + # C1: wavelength provided + (["--wavelength", "0.25"], {"wavelength": 0.25, "anode_type": None}), + # C2: anode type provided + (["--anode-type", "Mo"], {"wavelength": None, "anode_type": "Mo"}), + # C4: both wavelength and anode type provided + ( + ["--wavelength", "0.7", "--anode-type", "Mo"], + {"wavelength": 0.7, "anode_type": "Mo"}, + ), + ], +) +def test_load_wavelength_from_config_file_without_conf_files( + mocker, user_filesystem, inputs, expected +): + cwd = Path(user_filesystem) + home_dir = cwd / "home_dir" + mocker.patch("pathlib.Path.home", lambda _: home_dir) + os.chdir(cwd) + confile = home_dir / "diffpyconfig.json" + os.remove(confile) + + cli_inputs = ["2.5", "data.xy"] + inputs actual_args = get_args(cli_inputs) actual_args = _load_wavelength_from_config_file(actual_args) - assert actual_args.wavelength == 0.25 + assert actual_args.wavelength == expected["wavelength"] + assert actual_args.anode_type == expected["anode_type"] @pytest.mark.parametrize( From c6c8b670cf1ddc25b332a3c022bf61fd34784f1a Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Mon, 24 Feb 2025 18:20:13 -0500 Subject: [PATCH 3/6] docs: add news --- news/wavelength-config.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/wavelength-config.rst diff --git a/news/wavelength-config.rst b/news/wavelength-config.rst new file mode 100644 index 0000000..ba90ac7 --- /dev/null +++ b/news/wavelength-config.rst @@ -0,0 +1,23 @@ +**Added:** + +* Functionality to allow users to specify wavelength or anode type in a diffpy config file. + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 79dae25ecf9da6b5d3f3b307e253895c7f751493 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 20 Apr 2025 13:46:49 -0400 Subject: [PATCH 4/6] feat: print message if no wavelength info in config; clarify test comments on loading vs validation --- news/wavelength-config.rst | 2 +- src/diffpy/labpdfproc/tools.py | 43 ++++++++++++++++++++++++---------- tests/test_tools.py | 28 +++++++++++++++------- 3 files changed, 51 insertions(+), 22 deletions(-) diff --git a/news/wavelength-config.rst b/news/wavelength-config.rst index ba90ac7..1bf4566 100644 --- a/news/wavelength-config.rst +++ b/news/wavelength-config.rst @@ -1,6 +1,6 @@ **Added:** -* Functionality to allow users to specify wavelength or anode type in a diffpy config file. +* Functionality to read wavelength and anode type directly from a diffpy configuration file. **Changed:** diff --git a/src/diffpy/labpdfproc/tools.py b/src/diffpy/labpdfproc/tools.py index 75b34ff..36e3961 100644 --- a/src/diffpy/labpdfproc/tools.py +++ b/src/diffpy/labpdfproc/tools.py @@ -160,9 +160,11 @@ def set_input_lists(args): return args -def _load_wavelength_from_config_file(args): +def load_wavelength_from_config_file(args): """Load wavelength and anode type from config files. - It takes cli inputs first, and local config, and then global config. + + It prioritizes values in the following order: + 1. cli inputs, 2. local config file, 3. global config file. Parameters ---------- @@ -174,16 +176,33 @@ def _load_wavelength_from_config_file(args): args : argparse.Namespace The updated arguments with the updated wavelength and anode type. """ - if args.wavelength or args.anode_type: - return args global_config = _load_config(Path().home() / "diffpyconfig.json") local_config = _load_config(Path().cwd() / "diffpyconfig.json") - if local_config: - args.wavelength = local_config.get("wavelength") - args.anode_type = local_config.get("anode_type") - elif global_config: - args.wavelength = global_config.get("wavelength") - args.anode_type = global_config.get("anode_type") + local_has_data = local_config and ( + "wavelength" in local_config or "anode_type" in local_config + ) + global_has_data = global_config and ( + "wavelength" in global_config or "anode_type" in global_config + ) + if not local_has_data and not global_has_data: + print( + "No configuration file was found containing information " + "about the wavelength or anode type. \n" + "You can add the wavelength or anode type " + "to a configuration file on the current computer " + "and it will be automatically associated with " + "subsequent diffpy data by default. \n" + "You will only have to do that once. \n" + "For more information, please refer to www.diffpy.org/" + "diffpy.labpdfproc/examples/toolsexample.html" + ) + + if args.wavelength or args.anode_type: + return args + config = local_config if local_has_data else global_config + if config: + args.wavelength = args.wavelength or config.get("wavelength") + args.anode_type = args.anode_type or config.get("anode_type") return args @@ -202,7 +221,7 @@ def set_wavelength(args): ------ ValueError Raised if: - (1) neither wavelength or anode type is provided, + (1) neither wavelength or anode type is provided and xtype is not the two-theta grid, (2) both are provided, (3) anode_type is not one of the known sources, @@ -213,7 +232,7 @@ def set_wavelength(args): args : argparse.Namespace The updated arguments with the wavelength. """ - args = _load_wavelength_from_config_file(args) + args = load_wavelength_from_config_file(args) if args.wavelength is None and args.anode_type is None: if args.xtype not in ANGLEQUANTITIES: raise ValueError( diff --git a/tests/test_tools.py b/tests/test_tools.py index ac5cfa5..a256aa8 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -7,12 +7,12 @@ from diffpy.labpdfproc.labpdfprocapp import get_args from diffpy.labpdfproc.tools import ( - _load_wavelength_from_config_file, known_sources, load_metadata, load_package_info, load_user_info, load_user_metadata, + load_wavelength_from_config_file, preprocessing_args, set_input_lists, set_mud, @@ -200,9 +200,13 @@ def test_set_output_directory_bad(user_filesystem): @pytest.mark.parametrize( "inputs, expected", [ - # Test when only a home config file exists (no local config file), - # expect to return args if wavelength or anode type is specified, - # otherwise update args with values from the home config file. + # Test with only a home config file (no local config), + # expect to return values directly from args + # if either wavelength or anode type is specified, + # otherwise update args with values from the home config file + # (wavelength=0.3, no anode type). + # This test only checks loading behavior, + # not value validation (which is handled by `set_wavelength`). # C1: no args, expect to update arg values from home config ([""], {"wavelength": 0.3, "anode_type": None}), # C2: wavelength provided, expect to return args unchanged @@ -227,7 +231,7 @@ def test_load_wavelength_from_config_file_with_home_conf_file( cli_inputs = ["2.5", "data.xy"] + inputs actual_args = get_args(cli_inputs) - actual_args = _load_wavelength_from_config_file(actual_args) + actual_args = load_wavelength_from_config_file(actual_args) assert actual_args.wavelength == expected["wavelength"] assert actual_args.anode_type == expected["anode_type"] @@ -236,9 +240,13 @@ def test_load_wavelength_from_config_file_with_home_conf_file( "inputs, expected", [ # Test when a local config file exists, - # expect to return args if wavelength or anode type is specified, - # otherwise update args with values from the home config file. + # expect to return values directly from args + # if either wavelength or anode type is specified, + # otherwise update args with values from the local config file + # (wavelength=0.6, no anode type). # Results should be the same whether if the home config exists. + # This test only checks loading behavior, + # not value validation (which is handled by `set_wavelength`). # C1: no args, expect to update arg values from local config ([""], {"wavelength": 0.6, "anode_type": None}), # C2: wavelength provided, expect to return args unchanged @@ -266,7 +274,7 @@ def test_load_wavelength_from_config_file_with_local_conf_file( cli_inputs = ["2.5", "data.xy"] + inputs actual_args = get_args(cli_inputs) - actual_args = _load_wavelength_from_config_file(actual_args) + actual_args = load_wavelength_from_config_file(actual_args) assert actual_args.wavelength == expected["wavelength"] assert actual_args.anode_type == expected["anode_type"] @@ -282,6 +290,8 @@ def test_load_wavelength_from_config_file_with_local_conf_file( [ # Test when no config files exist, # expect to return args without modification. + # This test only checks loading behavior, + # not value validation (which is handled by `set_wavelength`). # C1: no args ([""], {"wavelength": None, "anode_type": None}), # C1: wavelength provided @@ -307,7 +317,7 @@ def test_load_wavelength_from_config_file_without_conf_files( cli_inputs = ["2.5", "data.xy"] + inputs actual_args = get_args(cli_inputs) - actual_args = _load_wavelength_from_config_file(actual_args) + actual_args = load_wavelength_from_config_file(actual_args) assert actual_args.wavelength == expected["wavelength"] assert actual_args.anode_type == expected["anode_type"] From d040e814bb5d76ccc209bf8945bb72202f8bafa8 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 20 Apr 2025 14:21:23 -0400 Subject: [PATCH 5/6] fix: update tests for updated muD cli behavior --- src/diffpy/labpdfproc/tools.py | 4 ++-- tests/test_tools.py | 19 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/src/diffpy/labpdfproc/tools.py b/src/diffpy/labpdfproc/tools.py index 7f990c5..ea5299d 100644 --- a/src/diffpy/labpdfproc/tools.py +++ b/src/diffpy/labpdfproc/tools.py @@ -256,7 +256,7 @@ def set_wavelength(args): ) if matched_anode_type is None: raise ValueError( - f"Anode type not recognized. " + f"Anode type '{args.anode_type}' not recognized. " f"Please rerun specifying an anode_type " f"from {*known_sources, }." ) @@ -264,7 +264,7 @@ def set_wavelength(args): args.wavelength = WAVELENGTHS[args.anode_type] elif args.wavelength is not None and args.wavelength <= 0: raise ValueError( - "No valid wavelength. " + f"Wavelength = {args.wavelength} is not valid. " "Please rerun specifying a known anode_type " "or a positive wavelength." ) diff --git a/tests/test_tools.py b/tests/test_tools.py index f5453af..60c4c19 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -214,7 +214,7 @@ def test_set_output_directory_bad(user_filesystem): # This test only checks loading behavior, # not value validation (which is handled by `set_wavelength`). # C1: no args, expect to update arg values from home config - ([""], {"wavelength": 0.3, "anode_type": None}), + ([], {"wavelength": 0.3, "anode_type": None}), # C2: wavelength provided, expect to return args unchanged (["--wavelength", "0.25"], {"wavelength": 0.25, "anode_type": None}), # C3: anode type provided, expect to return args unchanged @@ -235,7 +235,7 @@ def test_load_wavelength_from_config_file_with_home_conf_file( mocker.patch("pathlib.Path.home", lambda _: home_dir) os.chdir(cwd) - cli_inputs = ["2.5", "data.xy"] + inputs + cli_inputs = ["data.xy", "--mud", "2.5"] + inputs actual_args = get_args(cli_inputs) actual_args = load_wavelength_from_config_file(actual_args) assert actual_args.wavelength == expected["wavelength"] @@ -254,7 +254,7 @@ def test_load_wavelength_from_config_file_with_home_conf_file( # This test only checks loading behavior, # not value validation (which is handled by `set_wavelength`). # C1: no args, expect to update arg values from local config - ([""], {"wavelength": 0.6, "anode_type": None}), + ([], {"wavelength": 0.6, "anode_type": None}), # C2: wavelength provided, expect to return args unchanged (["--wavelength", "0.25"], {"wavelength": 0.25, "anode_type": None}), # C3: anode type provided, expect to return args unchanged @@ -278,7 +278,7 @@ def test_load_wavelength_from_config_file_with_local_conf_file( with open(cwd / "diffpyconfig.json", "w") as f: json.dump(local_config_data, f) - cli_inputs = ["2.5", "data.xy"] + inputs + cli_inputs = ["data.xy", "--mud", "2.5"] + inputs actual_args = get_args(cli_inputs) actual_args = load_wavelength_from_config_file(actual_args) assert actual_args.wavelength == expected["wavelength"] @@ -299,7 +299,7 @@ def test_load_wavelength_from_config_file_with_local_conf_file( # This test only checks loading behavior, # not value validation (which is handled by `set_wavelength`). # C1: no args - ([""], {"wavelength": None, "anode_type": None}), + ([], {"wavelength": None, "anode_type": None}), # C1: wavelength provided (["--wavelength", "0.25"], {"wavelength": 0.25, "anode_type": None}), # C2: anode type provided @@ -321,7 +321,7 @@ def test_load_wavelength_from_config_file_without_conf_files( confile = home_dir / "diffpyconfig.json" os.remove(confile) - cli_inputs = ["2.5", "data.xy"] + inputs + cli_inputs = ["data.xy", "--mud", "2.5"] + inputs actual_args = get_args(cli_inputs) actual_args = load_wavelength_from_config_file(actual_args) assert actual_args.wavelength == expected["wavelength"] @@ -406,13 +406,13 @@ def test_set_wavelength(inputs, expected): ( # C3: invalid anode type # expect error asking to specify a valid anode type ["--anode-type", "invalid"], - f"Anode type not recognized. " + f"Anode type 'invalid' not recognized. " f"Please rerun specifying an anode_type from {*known_sources, }.", ), ( # C4: invalid wavelength # expect error asking to specify a valid wavelength or anode type - ["--wavelength", "0"], - "No valid wavelength. " + ["--wavelength", "-0.2"], + "Wavelength = -0.2 is not valid. " "Please rerun specifying a known anode_type " "or a positive wavelength.", ), @@ -421,6 +421,7 @@ def test_set_wavelength(inputs, expected): def test_set_wavelength_bad(inputs, expected_error_msg): cli_inputs = ["data.xy", "--mud", "2.5"] + inputs actual_args = get_args(cli_inputs) + print(actual_args.wavelength) with pytest.raises(ValueError, match=re.escape(expected_error_msg)): actual_args = set_wavelength(actual_args) From b19d44ac1e82f85ed854122bc4115a13fb845fe8 Mon Sep 17 00:00:00 2001 From: yucongalicechen Date: Sun, 20 Apr 2025 14:24:53 -0400 Subject: [PATCH 6/6] remove debug print message --- tests/test_tools.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_tools.py b/tests/test_tools.py index 60c4c19..9ccc4b3 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -421,7 +421,6 @@ def test_set_wavelength(inputs, expected): def test_set_wavelength_bad(inputs, expected_error_msg): cli_inputs = ["data.xy", "--mud", "2.5"] + inputs actual_args = get_args(cli_inputs) - print(actual_args.wavelength) with pytest.raises(ValueError, match=re.escape(expected_error_msg)): actual_args = set_wavelength(actual_args)