Skip to content

Commit 6823cff

Browse files
authored
Merge pull request #780 from astronomerritt/configs_command
Adding command to copy config files.
2 parents a25d1b1 + 7da2ec6 commit 6823cff

File tree

3 files changed

+229
-0
lines changed

3 files changed

+229
-0
lines changed

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ sorcha = "sorcha.sorcha:main"
4141
makeSLURMscript = "sorcha.utilities.makeSLURMscript:main"
4242
createResultsSQLDatabase = "sorcha.utilities.createResultsSQLDatabase:main"
4343
bootstrap_sorcha_data_files = "sorcha.utilities.retrieve_ephemeris_data_files:main"
44+
sorcha_copy_configs = "sorcha.utilities.sorcha_copy_configs:main"
4445

4546
[project.urls]
4647
"Documentation" = "https://sorcha.readthedocs.io/en/latest/"
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import os
2+
import argparse
3+
from pathlib import Path
4+
import shutil
5+
import sys
6+
7+
from sorcha.modules.PPConfigParser import PPFindDirectoryOrExit
8+
9+
10+
def copy_demo_configs(copy_location, which_configs, force_overwrite):
11+
"""
12+
Copies the example Sorcha configuration files to a user-specified location.
13+
14+
Parameters
15+
-----------
16+
copy_location : string
17+
String containing the filepath of the location to which the configuration files should be copied.
18+
19+
which_configs : string
20+
String indicating which configuration files to retrieve. Should be "rubin", "demo" or "all".
21+
22+
force_overwrite: boolean
23+
Flag for determining whether existing files should be overwritten.
24+
25+
Returns
26+
-----------
27+
None
28+
29+
"""
30+
31+
_ = PPFindDirectoryOrExit(copy_location, "filepath")
32+
33+
path_to_file = os.path.abspath(__file__)
34+
35+
path_to_surveys = os.path.join(str(Path(path_to_file).parents[3]), "survey_setups")
36+
path_to_demo = os.path.join(str(Path(path_to_file).parents[3]), "demo")
37+
38+
if which_configs == "rubin_circle":
39+
config_locations = [os.path.join(path_to_surveys, "Rubin_circular_approximation.ini")]
40+
elif which_configs == "rubin_footprint":
41+
config_locations = [os.path.join(path_to_surveys, "Rubin_full_footprint.ini")]
42+
elif which_configs == "all":
43+
config_locations = [
44+
os.path.join(path_to_surveys, "Rubin_circular_approximation.ini"),
45+
os.path.join(path_to_surveys, "Rubin_full_footprint.ini"),
46+
]
47+
else:
48+
sys.exit(
49+
"String '{}' not recognised for 'configs' variable. Must be 'rubin_circle', 'rubin_footprint' or 'all'.".format(
50+
which_configs
51+
)
52+
)
53+
54+
for config in config_locations:
55+
if not force_overwrite and os.path.isfile(config):
56+
sys.exit("Identical file exists at location. Re-run with -f or --force to force overwrite.")
57+
58+
shutil.copy(config, copy_location)
59+
60+
print("Example configuration files {} copied to {}.".format(config_locations, copy_location))
61+
62+
63+
def parse_file_selection(file_select):
64+
"""Turns the number entered by the user at the command line into a string
65+
prompt. Also performs error handling.
66+
67+
Parameters
68+
-----------
69+
file_select : int
70+
Integer entered by the user at command line.
71+
72+
Returns
73+
-----------
74+
which_configs : string
75+
String indicating which configuration files to retrieve. Should be "rubin", "demo" or "all".
76+
77+
"""
78+
79+
try:
80+
file_select = int(file_select)
81+
except ValueError:
82+
sys.exit("Input could not be converted to a valid integer. Please try again.")
83+
84+
if file_select not in [1, 2, 3]:
85+
sys.exit("Input could not be converted to a valid integer. Please input an integer between 1 and 3.")
86+
87+
selection_dict = {1: "rubin_circle", 2: "rubin_footprint", 3: "all"}
88+
89+
which_configs = selection_dict[file_select]
90+
91+
return which_configs
92+
93+
94+
def main():
95+
"""
96+
Copies example configuration files for Sorcha from the installation location
97+
to a user-specified location. Filepath to copy files to is specified by command-line
98+
flag. Selection of configuration files is done via user input.
99+
100+
usage: sorcha_copy_configs [-h] [-p PATH]
101+
arguments:
102+
-h, --help Show this help message and exit.
103+
[-p PATH, --path PATH] Filepath where you want to copy the config files. Default is current working directory.
104+
105+
Parameters
106+
-----------
107+
None
108+
109+
Returns
110+
-----------
111+
None
112+
113+
"""
114+
115+
parser = argparse.ArgumentParser(
116+
description="Copies example Sorcha configuration files to a user-specified location."
117+
)
118+
119+
parser.add_argument(
120+
"-p",
121+
"--path",
122+
help="Filepath where you want to copy the config files. Default is current working directory.",
123+
type=str,
124+
default="./",
125+
)
126+
127+
parser.add_argument(
128+
"-f",
129+
"--force",
130+
help="Force deletion/overwrite of existing config file(s). Default False.",
131+
action="store_true",
132+
default=False,
133+
)
134+
135+
args = parser.parse_args()
136+
137+
print("\nWhich configuration file(s) would you like to copy?:\n")
138+
print("1. Rubin-specific configuration file using circular approximation of camera footprint (faster).\n")
139+
print("2. Rubin-specific configuration file using full camera footprint (slower, but more accurate).\n")
140+
print("3. All.\n")
141+
file_select = input("Please enter a number and hit Return/Enter.\n")
142+
143+
which_configs = parse_file_selection(file_select)
144+
145+
copy_location = os.path.abspath(args.path)
146+
copy_demo_configs(copy_location, which_configs, args.force)
147+
148+
149+
if __name__ == "__main__":
150+
main()
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import os
2+
import pytest
3+
4+
5+
def test_sorcha_copy_configs(tmp_path):
6+
from sorcha.utilities.sorcha_copy_configs import copy_demo_configs
7+
8+
# test that the Rubin files are successfully copied
9+
copy_demo_configs(tmp_path, "rubin_circle", True)
10+
11+
assert os.path.isfile(os.path.join(tmp_path, "Rubin_circular_approximation.ini"))
12+
13+
copy_demo_configs(tmp_path, "rubin_footprint", True)
14+
15+
assert os.path.isfile(os.path.join(tmp_path, "Rubin_full_footprint.ini"))
16+
17+
# remove those files
18+
os.remove(os.path.join(tmp_path, "Rubin_circular_approximation.ini"))
19+
os.remove(os.path.join(tmp_path, "Rubin_full_footprint.ini"))
20+
21+
# test that all the configs are successfully copied
22+
copy_demo_configs(tmp_path, "all", True)
23+
24+
assert os.path.isfile(os.path.join(tmp_path, "Rubin_circular_approximation.ini"))
25+
assert os.path.isfile(os.path.join(tmp_path, "Rubin_full_footprint.ini"))
26+
27+
# test the error message if user supplies non-existent directory
28+
dummy_folder = os.path.join(tmp_path, "dummy_folder")
29+
with pytest.raises(SystemExit) as e:
30+
copy_demo_configs(dummy_folder, "all", True)
31+
32+
assert e.value.code == "ERROR: filepath {} supplied for filepath argument does not exist.".format(
33+
dummy_folder
34+
)
35+
36+
# test the error message if user supplies unrecognised keyword for which_configs variable
37+
with pytest.raises(SystemExit) as e2:
38+
copy_demo_configs(tmp_path, "laphroaig", True)
39+
40+
assert (
41+
e2.value.code
42+
== "String 'laphroaig' not recognised for 'configs' variable. Must be 'rubin_circle', 'rubin_footprint' or 'all'."
43+
)
44+
45+
# test tthe error message if file exists and overwrite isn't forced
46+
47+
with pytest.raises(SystemExit) as e3:
48+
copy_demo_configs(tmp_path, "rubin_footprint", False)
49+
50+
assert e3.value.code == "Identical file exists at location. Re-run with -f or --force to force overwrite."
51+
52+
53+
def test_parse_file_selection():
54+
from sorcha.utilities.sorcha_copy_configs import parse_file_selection
55+
56+
# test to make sure the inputs align with the correct options
57+
test_rubin_circle = parse_file_selection("1")
58+
test_rubin_footprint = parse_file_selection("2")
59+
test_all = parse_file_selection("3")
60+
61+
assert test_rubin_circle == "rubin_circle"
62+
assert test_rubin_footprint == "rubin_footprint"
63+
assert test_all == "all"
64+
65+
# test error messages
66+
67+
with pytest.raises(SystemExit) as e:
68+
test_string = parse_file_selection("not_an_integer")
69+
70+
assert e.value.code == "Input could not be converted to a valid integer. Please try again."
71+
72+
with pytest.raises(SystemExit) as e2:
73+
test_wrong_integer = parse_file_selection("4")
74+
75+
assert (
76+
e2.value.code
77+
== "Input could not be converted to a valid integer. Please input an integer between 1 and 3."
78+
)

0 commit comments

Comments
 (0)