From 9efd284ce15eb9a2a3e1eef290ee38180584a961 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:06:22 +0000 Subject: [PATCH 1/3] Initial plan From 3d63e683140af64048e869389ba27725adff03d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 20 Nov 2025 18:12:43 +0000 Subject: [PATCH 2/3] Add saveArgs() function to automatically save argparse command line arguments Co-authored-by: joey-kilgore <22432499+joey-kilgore@users.noreply.github.com> --- .gitignore | 4 +++ README.md | 32 ++++++++++++++++++++++ src/SimLogger/SimLogger.py | 39 ++++++++++++++++++++++++++ tests/test_SimLogger.py | 56 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 131 insertions(+) diff --git a/.gitignore b/.gitignore index 48b85e1..d996915 100644 --- a/.gitignore +++ b/.gitignore @@ -164,3 +164,7 @@ cython_debug/ # linting .ruff_cache + +# SimLogger data directory (test artifacts) +data/ + diff --git a/README.md b/README.md index c317085..91cd2c3 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,38 @@ print(sampleArrayLoaded) # [1, 2, 3, 4] ``` +## Autosave Command Line Arguments +Never forget to save your argparse CLI arguments again! SimLogger provides a convenient `saveArgs()` function that automatically saves all command-line arguments from argparse. + +```python +import argparse +from SimLogger import SimLogger + +parser = argparse.ArgumentParser() +parser.add_argument('--learning_rate', type=float, default=0.001) +parser.add_argument('--epochs', type=int, default=100) +parser.add_argument('--model_name', type=str, default='resnet50') +args = parser.parse_args() + +# Automatically save all CLI arguments +simTag = 'myExperiment' +SimLogger.saveArgs(simTag, args) + +# Outputs +# 2024-06-25 10:44:44,063 [INFO ] Logger Loaded +# 2024-06-25 10:44:44,064 [INFO ] ARGS,myExperiment,saved,data/obj/myExperiment_args_2024-06-25_10-44-44.pkl +# 2024-06-25 10:44:44,064 [INFO ] ARG,myExperiment,learning_rate,0.001 +# 2024-06-25 10:44:44,064 [INFO ] ARG,myExperiment,epochs,100 +# 2024-06-25 10:44:44,064 [INFO ] ARG,myExperiment,model_name,resnet50 +``` + +Later, you can load the saved arguments: +```python +from SimLogger import SimLogger +loaded_args = SimLogger.getObj('myExperiment', 'args') +print(loaded_args['learning_rate']) # 0.001 +``` + ## Installation Install the python package ```pip install SimLogger``` diff --git a/src/SimLogger/SimLogger.py b/src/SimLogger/SimLogger.py index 657daf2..7a0b8c6 100644 --- a/src/SimLogger/SimLogger.py +++ b/src/SimLogger/SimLogger.py @@ -64,6 +64,45 @@ def logNotes(notes): logging.info(notes) +def saveArgs(simTag, args, objFolder=os.path.join("data", "obj"), makeNote=True): + """Save argparse command line arguments to a pickle file and optionally log them. + This is a convenience function to automatically save CLI arguments from argparse. + + Args: + simTag (str): Unique tag for the simulation + args (argparse.Namespace): Parsed arguments from argparse + ArgumentParser.parse_args() + objFolder (str): Folder where the pickled file will be saved + makeNote (bool): Sets whether an additional note is made about the + arguments + + Returns: + filePath (str): the unique file path where the pickled arguments are saved + + Example: + >>> import argparse + >>> from SimLogger import SimLogger + >>> parser = argparse.ArgumentParser() + >>> parser.add_argument('--param1', type=int, default=10) + >>> parser.add_argument('--param2', type=str, default='test') + >>> args = parser.parse_args() + >>> SimLogger.saveArgs('mySimulation', args) + """ + # Convert argparse.Namespace to dictionary for better readability + args_dict = vars(args) + + # Save the arguments as a pickled object + filePath = saveObj(simTag, "args", args_dict, objFolder=objFolder, makeNote=False) + + # Log each argument individually for easy reference + if makeNote: + logNotes("ARGS," + simTag + ",saved," + filePath) + for key, value in args_dict.items(): + logNotes("ARG," + simTag + "," + key + "," + str(value)) + + return filePath + + def saveObj(simTag, objTag, obj, objFolder=os.path.join("data", "obj"), makeNote=False): """Save an object (obj) to pickle file in the object folder. The file name will be the {simTag}_{objTag}_{dateTimeString}.pkl diff --git a/tests/test_SimLogger.py b/tests/test_SimLogger.py index 90bc4a5..c74fea6 100644 --- a/tests/test_SimLogger.py +++ b/tests/test_SimLogger.py @@ -1,4 +1,5 @@ from SimLogger import SimLogger +import argparse def test_save_and_get(): @@ -10,3 +11,58 @@ def test_save_and_get(): aLoadedObjTag = SimLogger.getObj(simTag, objTag) assert a == aLoadedUniqueId assert a == aLoadedObjTag + + +def test_save_args(): + # Create a simple argparse parser and parse some test arguments + parser = argparse.ArgumentParser() + parser.add_argument("--param1", type=int, default=42) + parser.add_argument("--param2", type=str, default="test_value") + parser.add_argument("--param3", type=float, default=3.14) + + # Parse with default values + args = parser.parse_args([]) + + simTag = "testing-args-simTag" + + # Save the args + filePath = SimLogger.saveArgs(simTag, args) + + # Verify the file was created + assert filePath is not None + assert "args" in filePath + assert simTag in filePath + + # Load the args back + loaded_args = SimLogger.getObj(simTag, "args") + + # Verify the loaded args match the original + assert loaded_args["param1"] == 42 + assert loaded_args["param2"] == "test_value" + assert loaded_args["param3"] == 3.14 + + +def test_save_args_with_custom_values(): + # Create a parser and parse with custom values + parser = argparse.ArgumentParser() + parser.add_argument("--learning_rate", type=float, default=0.001) + parser.add_argument("--epochs", type=int, default=100) + parser.add_argument("--model_name", type=str, default="default") + + # Parse with custom values + args = parser.parse_args( + ["--learning_rate", "0.01", "--epochs", "50", "--model_name", "custom_model"] + ) + + simTag = "testing-custom-args" + + # Save the args + SimLogger.saveArgs(simTag, args, makeNote=True) + + # Load the args back + loaded_args = SimLogger.getObj(simTag, "args") + + # Verify the loaded args match the custom values + assert loaded_args["learning_rate"] == 0.01 + assert loaded_args["epochs"] == 50 + assert loaded_args["model_name"] == "custom_model" From cb978274be7b802cde9392636de47b949a0571bd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:50:04 +0000 Subject: [PATCH 3/3] Add fully automatic argument saving with ArgumentParser and setSimTag Co-authored-by: joey-kilgore <22432499+joey-kilgore@users.noreply.github.com> --- README.md | 39 ++++++++++++++--- src/SimLogger/SimLogger.py | 88 ++++++++++++++++++++++++++++++++++++++ tests/test_SimLogger.py | 49 +++++++++++++++++++++ 3 files changed, 169 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 91cd2c3..1efcbe0 100644 --- a/README.md +++ b/README.md @@ -38,21 +38,25 @@ print(sampleArrayLoaded) ``` ## Autosave Command Line Arguments -Never forget to save your argparse CLI arguments again! SimLogger provides a convenient `saveArgs()` function that automatically saves all command-line arguments from argparse. +Never forget to save your argparse CLI arguments again! SimLogger provides **automatic** saving of command-line arguments. + +### Fully Automatic (Recommended) +Use `SimLogger.ArgumentParser` instead of `argparse.ArgumentParser` for completely automatic saving: ```python -import argparse from SimLogger import SimLogger -parser = argparse.ArgumentParser() +# Set your simulation tag once +SimLogger.setSimTag('myExperiment') + +# Use SimLogger.ArgumentParser instead of argparse.ArgumentParser +parser = SimLogger.ArgumentParser(description='My Experiment') parser.add_argument('--learning_rate', type=float, default=0.001) parser.add_argument('--epochs', type=int, default=100) parser.add_argument('--model_name', type=str, default='resnet50') -args = parser.parse_args() -# Automatically save all CLI arguments -simTag = 'myExperiment' -SimLogger.saveArgs(simTag, args) +# Arguments are AUTOMATICALLY saved when you call parse_args()! +args = parser.parse_args() # Outputs # 2024-06-25 10:44:44,063 [INFO ] Logger Loaded @@ -62,6 +66,27 @@ SimLogger.saveArgs(simTag, args) # 2024-06-25 10:44:44,064 [INFO ] ARG,myExperiment,model_name,resnet50 ``` +You can also pass the `simTag` directly to the parser: +```python +parser = SimLogger.ArgumentParser(simTag='myExperiment') +``` + +### Manual Saving +If you prefer to manually control when arguments are saved, you can use the `saveArgs()` function: + +```python +import argparse +from SimLogger import SimLogger + +parser = argparse.ArgumentParser() +parser.add_argument('--learning_rate', type=float, default=0.001) +args = parser.parse_args() + +# Manually save all CLI arguments +SimLogger.saveArgs('myExperiment', args) +``` + +### Loading Saved Arguments Later, you can load the saved arguments: ```python from SimLogger import SimLogger diff --git a/src/SimLogger/SimLogger.py b/src/SimLogger/SimLogger.py index 7a0b8c6..32e15a1 100644 --- a/src/SimLogger/SimLogger.py +++ b/src/SimLogger/SimLogger.py @@ -5,8 +5,10 @@ import pandas as pd import os from git import Repo +import argparse isLoaded = False +_simTag = None # Global variable to store the simulation tag for auto-saving def setupLogger(fileName="example.log", githubLink=None): @@ -267,3 +269,89 @@ def isSimTagUsed(simTag, objFolder=os.path.join("data", "obj")): if len(fileList) > 0: return False return True + + +class ArgumentParser(argparse.ArgumentParser): + """Custom ArgumentParser that automatically saves arguments when + parse_args() is called. + + This class extends argparse.ArgumentParser to provide automatic saving of + command-line arguments. When parse_args() is called, the arguments are + automatically saved using saveArgs() if a simulation tag has been set. + + To use this feature: + 1. Set the simulation tag using setSimTag() before parsing arguments + 2. Use SimLogger.ArgumentParser instead of argparse.ArgumentParser + 3. Call parse_args() as usual - arguments will be saved automatically + + Example: + >>> from SimLogger import SimLogger + >>> SimLogger.setSimTag('myExperiment') + >>> parser = SimLogger.ArgumentParser() + >>> parser.add_argument('--learning_rate', type=float, default=0.001) + >>> args = parser.parse_args() # Arguments are automatically saved! + """ + + def __init__(self, *args, simTag=None, autoSave=True, **kwargs): + """Initialize the ArgumentParser. + + Args: + simTag (str): Optional simulation tag for auto-saving arguments. + If not provided, uses the global simTag set by setSimTag(). + autoSave (bool): Whether to automatically save arguments when + parse_args() is called. Default is True. + *args, **kwargs: All other arguments are passed to + argparse.ArgumentParser + """ + super().__init__(*args, **kwargs) + self._simTag = simTag + self._autoSave = autoSave + + def parse_args(self, args=None, namespace=None): + """Parse arguments and automatically save them if autoSave is enabled. + + Args: + args: List of strings to parse. If None, uses sys.argv. + namespace: Object to populate with parsed arguments. + + Returns: + Namespace object with parsed arguments + """ + parsed_args = super().parse_args(args, namespace) + + # Auto-save if enabled and we have a simTag + if self._autoSave: + simTag = self._simTag if self._simTag is not None else _simTag + if simTag is not None: + saveArgs(simTag, parsed_args) + else: + # Log a warning if auto-save is enabled but no simTag is set + global isLoaded + if not isLoaded: + setupLogger() + logging.warning( + "ArgumentParser auto-save is enabled but no simTag is set. " + "Use setSimTag() or pass simTag to ArgumentParser constructor." + ) + + return parsed_args + + +def setSimTag(simTag): + """Set the global simulation tag for automatic argument saving. + + This sets the simulation tag that will be used by SimLogger.ArgumentParser + for automatic saving of parsed arguments. + + Args: + simTag (str): Unique tag for the simulation + + Example: + >>> from SimLogger import SimLogger + >>> SimLogger.setSimTag('myExperiment') + >>> parser = SimLogger.ArgumentParser() + >>> parser.add_argument('--param1', type=int) + >>> args = parser.parse_args() # Automatically saved to 'myExperiment' + """ + global _simTag + _simTag = simTag diff --git a/tests/test_SimLogger.py b/tests/test_SimLogger.py index c74fea6..05b44e7 100644 --- a/tests/test_SimLogger.py +++ b/tests/test_SimLogger.py @@ -66,3 +66,52 @@ def test_save_args_with_custom_values(): assert loaded_args["learning_rate"] == 0.01 assert loaded_args["epochs"] == 50 assert loaded_args["model_name"] == "custom_model" + + +def test_argument_parser_auto_save_with_simTag_param(): + # Test automatic saving using ArgumentParser with simTag parameter + parser = SimLogger.ArgumentParser(simTag="auto-save-test-1") + parser.add_argument("--param1", type=int, default=100) + parser.add_argument("--param2", type=str, default="auto_test") + + # Parse args - should automatically save + parser.parse_args([]) + + # Verify the args were automatically saved + loaded_args = SimLogger.getObj("auto-save-test-1", "args") + assert loaded_args["param1"] == 100 + assert loaded_args["param2"] == "auto_test" + + +def test_argument_parser_auto_save_with_setSimTag(): + # Test automatic saving using ArgumentParser with global setSimTag + SimLogger.setSimTag("auto-save-test-2") + parser = SimLogger.ArgumentParser() + parser.add_argument("--learning_rate", type=float, default=0.002) + parser.add_argument("--batch_size", type=int, default=64) + + # Parse args - should automatically save using global simTag + parser.parse_args(["--learning_rate", "0.005", "--batch_size", "128"]) + + # Verify the args were automatically saved + loaded_args = SimLogger.getObj("auto-save-test-2", "args") + assert loaded_args["learning_rate"] == 0.005 + assert loaded_args["batch_size"] == 128 + + +def test_argument_parser_auto_save_disabled(): + # Test that auto-save can be disabled + parser = SimLogger.ArgumentParser(simTag="should-not-save", autoSave=False) + parser.add_argument("--param1", type=int, default=999) + + # Parse args - should NOT automatically save + parser.parse_args([]) + + # Verify the args were NOT saved + import os + + objFolder = os.path.join("data", "obj") + fileList = os.listdir(objFolder) + matching_files = [f for f in fileList if "should-not-save" in f] + # Should have no files since auto-save was disabled + assert len(matching_files) == 0