Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions docs/changes/2096.maintenance.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Consolidate usage of command line configuration; removed duplicated items and solved name conflicts (e.g. zenith vs zenith_angle).
107 changes: 107 additions & 0 deletions src/simtools/application_control.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Application control utilities for startup and shutdown simtools applications."""

import inspect
import logging
import os
import re
Expand All @@ -9,6 +10,7 @@

import simtools.utils.general as gen
from simtools import dependencies, version
from simtools.configuration import configurator
from simtools.db import db_handler
from simtools.io import io_handler
from simtools.settings import config
Expand Down Expand Up @@ -140,6 +142,83 @@ class ApplicationContext:
io_handler: io_handler.IOHandler | None


def build_application(
application_path=None,
description=None,
add_arguments_function=None,
initialization_kwargs=None,
startup_kwargs=None,
usage=None,
epilog=None,
parse_function=None,
):
"""
Build and start an application using the standard simtools startup flow.

Parameters
----------
application_path : str, optional
Application file path, typically ``__file__``.
If not provided, it is inferred from the caller module.
description : str, optional
Application description shown in the CLI help (reduced to first line).
If not provided, it is inferred from the caller module docstring.
add_arguments_function : callable, optional
Function receiving the application's ``CommandLineParser`` instance to register
application-specific arguments. If not provided, ``_add_arguments`` from the
caller module is used when available.
initialization_kwargs : dict, optional
Keyword arguments forwarded to ``Configurator.initialize``.
startup_kwargs : dict, optional
Keyword arguments forwarded to ``startup_application``.
usage : str, optional
CLI usage string.
epilog : str, optional
CLI epilog.
parse_function : callable, optional
Existing parser function returning ``(args_dict, db_config)``. If provided,
``build_application`` delegates directly to ``startup_application`` with this
parser function.

Returns
-------
ApplicationContext
Application context returned by ``startup_application``.
"""
initialization_kwargs = initialization_kwargs or {}
startup_kwargs = startup_kwargs or {}

if application_path is None or description is None or add_arguments_function is None:
caller_globals = inspect.currentframe().f_back.f_globals
if application_path is None:
application_path = caller_globals.get("__file__")
if description is None:
description = caller_globals.get("__doc__")
if add_arguments_function is None:
add_arguments_function = caller_globals.get("_add_arguments")

if application_path is None:
raise ValueError("Missing application path; provide application_path explicitly.")
if description is None:
raise ValueError("Missing description; provide description explicitly.")

if parse_function is not None:
return startup_application(parse_function, **startup_kwargs)

def _parse():
config_builder = configurator.Configurator(
label=get_application_label(application_path),
usage=usage,
description=get_module_description_line(description),
epilog=epilog,
)
if add_arguments_function is not None:
add_arguments_function(config_builder.parser)
return config_builder.initialize(**initialization_kwargs)

return startup_application(_parse, **startup_kwargs)


def startup_application(
parse_function,
setup_io_handler=True,
Expand Down Expand Up @@ -253,6 +332,34 @@ def main():
return Path(file_path).stem


def get_module_description_line(docstring):
"""Return the first non-empty line from a docstring.

Parameters
----------
docstring : str
Module docstring (typically from __doc__).

Returns
-------
str
First non-empty line from the docstring.

Raises
------
ValueError
If docstring is None or empty.
"""
if not docstring:
raise ValueError("Missing or empty docstring")

for line in docstring.splitlines():
if line.strip():
return line.strip()

raise ValueError("Empty docstring (only whitespace)")


def _resolve_model_version_to_latest_patch(args_dict, logger):
"""
Update model_version in args_dict to latest patch version if needed.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,40 +65,33 @@
import numpy as np

import simtools.data_model.model_data_writer as writer
from simtools.application_control import get_application_label, startup_application
from simtools.configuration import configurator
from simtools.application_control import build_application
from simtools.data_model import schema
from simtools.io import ascii_handler
from simtools.simtel import simtel_config_reader


def _parse():
"""Parse command line configuration."""
config = configurator.Configurator(
label=get_application_label(__file__),
description="Convert all model parameters from sim_telarray",
)

config.parser.add_argument(
def _add_arguments(parser):
"""Register application-specific command line arguments."""
parser.add_argument(
"--simtel_cfg_file",
help="File name for sim_telarray configuration",
type=str,
required=True,
)
config.parser.add_argument(
parser.add_argument(
"--simtel_telescope_name",
help="Name of the telescope in the sim_telarray configuration file",
type=str,
required=True,
)
config.parser.add_argument(
parser.add_argument(
"--skip_parameter",
help="List of parameters to be skipped.",
type=str,
nargs="*",
default=[],
)
return config.initialize(simulation_model=["telescope", "parameter_version"])


def read_simtel_config_file(args_dict, schema_file, camera_pixels=None):
Expand Down Expand Up @@ -310,8 +303,10 @@ def print_list_of_files(args_dict, logger):


def main():
"""Convert all simulation model parameters exported from sim_telarray format."""
app_context = startup_application(_parse)
"""See CLI description."""
app_context = build_application(
initialization_kwargs={"simulation_model": ["telescope", "parameter_version"]},
)

_parameters_not_in_simtel, _simtel_parameters = read_and_export_parameters(
app_context.args, app_context.logger, app_context.io_handler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,31 +55,25 @@
"""

import simtools.data_model.model_data_writer as writer
from simtools.application_control import get_application_label, startup_application
from simtools.configuration import configurator
from simtools.application_control import build_application
from simtools.data_model.metadata_collector import MetadataCollector
from simtools.layout import array_layout


def _parse():
"""Parse command line configuration."""
config = configurator.Configurator(
label=get_application_label(__file__),
description="Print a list of array element positions",
)

config.parser.add_argument(
def _add_arguments(parser):
"""Register application-specific command line arguments."""
parser.add_argument(
"--input",
help="list of array element positions",
required=True,
)
config.parser.add_argument(
parser.add_argument(
"--input_meta",
help="meta data file associated to input data",
type=str,
required=False,
)
config.parser.add_argument(
parser.add_argument(
"--print",
help="print list of positions in requested coordinate system",
required=False,
Expand All @@ -90,7 +84,7 @@ def _parse():
"mercator",
],
)
config.parser.add_argument(
parser.add_argument(
"--export",
help="export array element list to file (in requested coordinate system)",
required=False,
Expand All @@ -101,31 +95,32 @@ def _parse():
"mercator",
],
)
config.parser.add_argument(
parser.add_argument(
"--select_assets",
help="select a subset of assets (e.g., MSTN, LSTN)",
required=False,
default=None,
nargs="+",
)
config.parser.add_argument(
parser.add_argument(
"--skip_input_validation",
help="skip input data validation against schema",
default=False,
required=False,
action="store_true",
)
return config.initialize(
db_config=True,
output=True,
require_command_line=True,
simulation_model=["model_version", "parameter_version", "site"],
)


def main():
"""Print a list of array elements."""
app_context = startup_application(_parse)
"""See CLI description."""
app_context = build_application(
initialization_kwargs={
"db_config": True,
"output": True,
"require_command_line": True,
"simulation_model": ["model_version", "parameter_version", "site"],
},
)

if app_context.args.get("input", "").endswith(".json"):
site = app_context.args.get("site", None)
Expand Down
29 changes: 14 additions & 15 deletions src/simtools/applications/convert_model_parameter_from_simtel.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,39 +36,38 @@
"""

import simtools.data_model.model_data_writer as writer
from simtools.application_control import get_application_label, startup_application
from simtools.configuration import configurator
from simtools.application_control import build_application
from simtools.simtel.simtel_config_reader import SimtelConfigReader


def _parse():
"""Parse command line configuration."""
config = configurator.Configurator(
label=get_application_label(__file__),
description="Convert simulation model parameter from sim_telarray to simtools format.",
)

config.parser.add_argument(
def _add_arguments(parser):
"""Register application-specific command line arguments."""
parser.add_argument(
"--schema", help="Schema file for model parameter validation", required=True
)
config.parser.add_argument(
parser.add_argument(
"--simtel_cfg_file",
help="File name for sim_telarray configuration",
type=str,
required=True,
)
config.parser.add_argument(
parser.add_argument(
"--simtel_telescope_name",
help="Name of the telescope in the sim_telarray configuration file",
type=str,
required=True,
)
return config.initialize(simulation_model=["telescope", "parameter_version"], output=True)


def main():
"""Convert simulation model parameter from sim_telarray to simtools format."""
app_context = startup_application(_parse, setup_io_handler=False)
"""See CLI description."""
app_context = build_application(
initialization_kwargs={
"simulation_model": ["telescope", "parameter_version"],
"output": True,
},
startup_kwargs={"setup_io_handler": False},
)

simtel_config_reader = SimtelConfigReader(
schema_file=app_context.args["schema"],
Expand Down
27 changes: 11 additions & 16 deletions src/simtools/applications/db_add_file_to_db.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,13 @@
from pathlib import Path

import simtools.utils.general as gen
from simtools.application_control import get_application_label, startup_application
from simtools.configuration import configurator
from simtools.application_control import build_application
from simtools.db import db_handler


def _parse():
"""Parse command line configuration."""
config = configurator.Configurator(
label=get_application_label(__file__),
description="Add file to the DB.",
usage="simtools-add-file-to-db --file_name test_application.dat --db test-data",
)
group = config.parser.add_mutually_exclusive_group(required=True)
def _add_arguments(parser):
"""Register application-specific command line arguments."""
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
"--file_name",
help=("The file name to upload. A list of files is also allowed."),
Expand All @@ -66,19 +60,17 @@ def _parse():
type=Path,
)

config.parser.add_argument(
parser.add_argument(
"--db",
type=str,
help=("The database to insert the files to."),
)
config.parser.add_argument(
parser.add_argument(
"--test_db",
help="Use sandbox database. Drop all data after the operation.",
action="store_true",
)

return config.initialize(paths=False, db_config=True)


def collect_files_to_insert(args_dict, logger, db):
"""
Expand Down Expand Up @@ -163,8 +155,11 @@ def confirm_and_insert_files(files_to_insert, args_dict, db, logger):


def main():
"""Add files to the database."""
app_context = startup_application(_parse, setup_io_handler=False)
"""See CLI description."""
app_context = build_application(
initialization_kwargs={"paths": False, "db_config": True},
startup_kwargs={"setup_io_handler": False},
)

db = db_handler.DatabaseHandler()

Expand Down
Loading
Loading