Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include comments in config files generated with --create-config-file #588

Merged
merged 4 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 49 additions & 6 deletions topostats/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@
LOGGER = logging.getLogger(LOGGER_NAME)


CONFIG_DOCUMENTATION_REFERENCE = """For more information on configuration and how to use it:
# https://afm-spm.github.io/TopoStats/main/configuration.html\n"""

# pylint: disable=broad-except


Expand All @@ -46,6 +49,23 @@ def read_yaml(filename: Union[str, Path]) -> Dict:
return {}


def get_date_time() -> str:
"""
Get a date and time for adding to generated files or logging.

Parameters
----------
None

Returns
-------
str
A string of the current date and time, formatted appropriately.
"""

return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


def write_yaml(
config: dict, output_dir: Union[str, Path], config_file: str = "config.yaml", header_message: str = None
) -> None:
Expand All @@ -67,17 +87,14 @@ def write_yaml(
# Revert PosixPath items to string
config = path_to_str(config)
config_yaml = yaml_load(yaml_dump(config))
documentation_reference = (
"For more information on configuration : https://afm-spm.github.io/TopoStats/main/configuration.html"
)

if header_message:
config_yaml.yaml_set_start_comment(
f"{header_message} : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" + documentation_reference
f"{header_message} : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" + CONFIG_DOCUMENTATION_REFERENCE
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could use the get_date_time() function here as you do below.

)
else:
config_yaml.yaml_set_start_comment(
f"Configuration from TopoStats run completed : {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n"
+ documentation_reference
f"Configuration from TopoStats run completed : {get_date_time()}\n" + CONFIG_DOCUMENTATION_REFERENCE
)
with output_config.open("w") as f:
try:
Expand All @@ -86,6 +103,32 @@ def write_yaml(
LOGGER.error(exception)


def write_config_with_comments(config: str, filename: str = "config.yaml"):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor pedantic typehint for the return value.

Suggested change
def write_config_with_comments(config: str, filename: str = "config.yaml"):
def write_config_with_comments(config: str, filename: str = "config.yaml") -> None:

"""
Create a config file, retaining the comments by writing it as a string
rather than using a yaml handling package.

Parameters
----------
config: str
A string of the entire configuration file to be saved.
filename: str
A name for the configuration file. Can have a ".yaml" on the end.
"""

if ".yaml" not in filename and ".yml" not in filename:
Copy link
Collaborator

@ns-rse ns-rse May 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to have this eventuality caught 👍

create_config_path = "./" + filename + ".yaml"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be overkill using Path.cwd() here and under the else: but could.

Suggested change
create_config_path = "./" + filename + ".yaml"
create_config_path = Path.cwd() / f"{filename}.yaml"

else:
create_config_path = "./" + filename

with open(f"./{create_config_path}", "w", encoding="utf-8") as f:
f.write(f"# Config file generated {get_date_time()}\n")
f.write(f"# {CONFIG_DOCUMENTATION_REFERENCE}")
f.write(config)
LOGGER.info(f"A sample configuration has been written to : ./{filename}")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
LOGGER.info(f"A sample configuration has been written to : ./{filename}")
LOGGER.info(f"A sample configuration has been written to : ./{str(create_config_path)}")

...although conditional on whether you use Path.cwd() / Path("./") / filename above. If not you don't need the str().

LOGGER.info(CONFIG_DOCUMENTATION_REFERENCE)


def save_array(array: np.ndarray, outpath: Path, filename: str, array_type: str) -> None:
"""Save a Numpy array to disk.

Expand Down
33 changes: 16 additions & 17 deletions topostats/run_topostats.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@
from tqdm import tqdm

from topostats import __version__
from topostats.io import find_files, read_yaml, save_folder_grainstats, write_yaml, LoadScans
from topostats.io import (
find_files,
read_yaml,
save_folder_grainstats,
write_yaml,
write_config_with_comments,
LoadScans,
)
from topostats.logs.logs import LOGGER_NAME
from topostats.plotting import toposum
from topostats.processing import check_run_steps, completion_message, process_scan
Expand Down Expand Up @@ -142,11 +149,12 @@ def main(args=None):
# Parse command line options, load config (or default) and update with command line options
parser = create_parser()
args = parser.parse_args() if args is None else parser.parse_args(args)
if args.config_file is not None:
config = read_yaml(args.config_file)
if args.config_file is None:
default_config = pkg_resources.open_text(__package__, "default_config.yaml").read()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, very neat trick of shifting the .read() to get the string representation of the file.

config = yaml.safe_load(default_config)
else:
default_config = pkg_resources.open_text(__package__, "default_config.yaml")
config = yaml.safe_load(default_config.read())
config = read_yaml(args.config_file)

config = update_config(config, args)

# Set logging level
Expand All @@ -162,19 +170,10 @@ def main(args=None):
validate_config(config, schema=DEFAULT_CONFIG_SCHEMA, config_type="YAML configuration file")

# Write sample configuration if asked to do so and exit
if args.create_config_file and args.config_file:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

raise ValueError("--create-config-file and --config cannot be used together.")
if args.create_config_file:
write_yaml(
config,
output_dir="./",
config_file=args.create_config_file,
header_message="Sample configuration file auto-generated",
)
LOGGER.info(f"A sample configuration has been written to : ./{args.create_config_file}")
LOGGER.info(
"Please refer to the documentation on how to use the configuration file : \n\n"
"https://afm-spm.github.io/TopoStats/usage.html#configuring-topostats\n"
"https://afm-spm.github.io/TopoStats/configuration.html"
)
write_config_with_comments(config=default_config, filename=args.create_config_file)
sys.exit()

# Create base output directory
Expand Down