Skip to content

Commit

Permalink
Merge pull request #477 from fast-aircraft-design/dev_source_file_gen
Browse files Browse the repository at this point in the history
MissionViewer fix and source file generation
  • Loading branch information
christophe-david committed Mar 6, 2023
2 parents 3f7902f + b847d19 commit 832fc57
Show file tree
Hide file tree
Showing 18 changed files with 843 additions and 111 deletions.
16 changes: 8 additions & 8 deletions src/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def with_no_plugin():
def with_dummy_plugin_1():
"""
Reduces plugin list to dummy-dist-1 with plugin test_plugin_1
(one configuration file, no models, notebook folder).
(one configuration file, no models, notebook folder, no source data files).
Any previous state of plugins is restored during teardown.
"""
Expand All @@ -118,7 +118,7 @@ def with_dummy_plugin_1():
def with_dummy_plugin_2():
"""
Reduces plugin list to dummy-dist-2 with plugin test_plugin_2
(one configuration file, model folder, no notebooks).
(one configuration file, model folder, no notebooks, no source data files).
Any previous state of plugins is restored during teardown.
"""
Expand All @@ -142,7 +142,7 @@ def with_dummy_plugin_2():
def with_dummy_plugin_4():
"""
Reduces plugin list to dummy-dist-1 with plugin test_plugin_4
(no configuration file, no model folder, notebooks).
(no configuration file, no model folder, notebooks, one source data file).
Any previous state of plugins is restored during teardown.
"""
Expand All @@ -166,7 +166,7 @@ def with_dummy_plugin_4():
def with_dummy_plugin_distribution_1():
"""
Reduces plugin list to dummy-dist-1 with plugins test_plugin_1 and test_plugin_4
(one configuration file, no models, notebook folder).
(one configuration file, no models, notebook folder, one source data file).
Any previous state of plugins is restored during teardown.
"""
Expand Down Expand Up @@ -197,7 +197,7 @@ def with_dummy_plugin_distribution_1():
def with_dummy_plugin_distribution_1_and_3():
"""
Reduces plugin list to dummy-dist-1 and dummy-dist-3
(one configuration file (in dist 1), models, notebook folder).
(one configuration file (in dist 1), models, notebook folder, one source data file (in dist 1)).
Any previous state of plugins is restored during teardown.
"""
Expand Down Expand Up @@ -236,11 +236,11 @@ def with_dummy_plugins():
"""
Reduces plugin list to:
- dummy-dist-1 with plugins test_plugin_1 and test_plugin_4
(one configuration file, no models, notebook folder).
(one configuration file, no models, notebook folder, one source data file).
- dummy-dist-2 with plugins test_plugin_2 and test_plugin_3
(3 configuration files, model folder, no notebooks).
(3 configuration files, model folder, no notebooks, 3 source data files).
- dummy-dist-3 with plugins test_plugin_5
(no configuration file, model folder, notebook folder).
(no configuration file, model folder, notebook folder, no source data files).
Any previous state of plugins is restored during teardown.
"""
Expand Down
1 change: 1 addition & 0 deletions src/fastoad/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
generate_notebooks,
evaluate_problem,
generate_configuration_file,
generate_source_data_file,
generate_inputs,
list_modules,
list_variables,
Expand Down
178 changes: 135 additions & 43 deletions src/fastoad/cmd/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
from collections.abc import Iterable
from time import time
from typing import Dict, IO, List, Union
from enum import Enum

import openmdao.api as om
import pandas as pd
Expand Down Expand Up @@ -56,6 +57,11 @@
_PROBLEM_CONFIGURATOR = None


class UserFileType(Enum):
CONFIGURATION = "configuration"
SOURCE_DATA = "source_data"


def get_plugin_information(print_data=False) -> Dict[str, DistributionPluginDefinition]:
"""
Provides information about available FAST-OAD plugins.
Expand Down Expand Up @@ -137,79 +143,165 @@ def generate_notebooks(
return destination_path


def generate_configuration_file(
configuration_file_path: str,
def _generate_user_file(
user_file_type: UserFileType,
user_file_path: str,
overwrite: bool = False,
distribution_name=None,
sample_file_name=None,
sample_user_file_name=None,
):
"""
Copies a sample configuration file from an available plugin.
:param configuration_file_path: the path of file to be written
Copies a sample user file from an available plugin. Since there are a lot of similarities
between the generation of source data files and configuration file, this generic method was
created to refactor their generation. The term user file is the generic term for
configuration files, source data files and inputs/outputs data files (not relevant here).
:param user_file_type: the type of user file that needs to be written, should match one
available in _AVAILABLE_USER_FILE_TYPE
:param user_file_path: the path of the user file to be written
:param overwrite: if True, the file will be written, even if it already exists
:param distribution_name: the name of the installed package that provides the sample
configuration file (can be omitted if only one plugin is available)
:param sample_file_name: the name of the sample configuration file (can be omitted if
the plugin provides only one configuration file)
user file (can be omitted if only one plugin is available)
:param sample_user_file_name: the name of the sample user file (can be omitted if the plugin
provides only one user file)
:return: path of generated file
:raise FastPathExistsError: if overwrite==False and configuration_file_path already exists
:raise FastPathExistsError: if overwrite==False and user_file_path already exists
"""

if distribution_name is None:
# If no distribution is specified, but only one contains configuration files, no need to
# specify the name
count_plugin_with_conf_file = 0
dist_with_conf_file = ""
plugin_config_files = {
name: [resource.name for resource in definition.get_configuration_file_list()]
for name, definition in get_plugin_information().items()
}
for plugin_name, conf_files in plugin_config_files.items():
# Plugin is retained if it contains conf files. Additionally, if sample_file_name
# is provided, plugin is retained only if sample_file_name is in provided conf files.
if len(conf_files) > 0 and (sample_file_name is None or sample_file_name in conf_files):
count_plugin_with_conf_file += 1
dist_with_conf_file = plugin_name
if count_plugin_with_conf_file > 1:
# If no distribution is specified, but only one contains the type of user files
# requested, no need to specify the name
count_plugin_with_user_file = 0
dist_with_user_file = ""

# Browse plugin and look for the type of user file requested
plugin_user_files = {}
for name, definition in get_plugin_information().items():
if user_file_type is UserFileType.CONFIGURATION:
matching_resources = [
resource.name for resource in definition.get_configuration_file_list()
]
else:
# Since we ensured the type is among the one we expect we can use the else,
# may need to be changed if other user file are added later on.
matching_resources = [
resource.name for resource in definition.get_source_data_file_list()
]

plugin_user_files[name] = matching_resources

for plugin_name, user_files in plugin_user_files.items():
# Plugin is retained if it contains the requested user files. Additionally, if
# sample_user_file_name is provided, plugin is retained only if sample_user_file_name
# is in provided user files.
if len(user_files) > 0 and (
sample_user_file_name is None or sample_user_file_name in user_files
):
count_plugin_with_user_file += 1
dist_with_user_file = plugin_name
if count_plugin_with_user_file > 1:
break

# If only one plugin has been retained, it can be used as automatically selected plugin.
if count_plugin_with_conf_file == 1:
distribution_name = dist_with_conf_file
if count_plugin_with_user_file == 1:
distribution_name = dist_with_user_file

dist_definition = FastoadLoader().get_distribution_plugin_definition(distribution_name)
file_info = dist_definition.get_configuration_file_info(sample_file_name)
if user_file_type is UserFileType.CONFIGURATION:
file_info = dist_definition.get_configuration_file_info(sample_user_file_name)
else:
# Since we ensured the type is among the one we expect we can use the else,
# may need to be changed if other user file are added later on.
file_info = dist_definition.get_source_data_file_info(sample_user_file_name)

# Check on file overwrite
configuration_file_path = pth.abspath(configuration_file_path)
if not overwrite and pth.exists(configuration_file_path):
user_file_path = pth.abspath(user_file_path)
if not overwrite and pth.exists(user_file_path):
raise FastPathExistsError(
f"Configuration file {configuration_file_path} not written because it already exists. "
user_file_type.value.capitalize()
+ f" {user_file_path} not written because it already exists. "
"Use overwrite=True to bypass.",
configuration_file_path,
user_file_path,
)

# Write file
make_parent_dir(configuration_file_path)
copy_resource(file_info.package_name, file_info.name, configuration_file_path)
_LOGGER.info('Sample configuration written in "%s".', configuration_file_path)
make_parent_dir(user_file_path)
copy_resource(file_info.package_name, file_info.name, user_file_path)
_LOGGER.info(
'Sample %s written in "%s".', user_file_type.value.replace("_", " "), user_file_path
)

return user_file_path


def generate_configuration_file(
configuration_file_path: str,
overwrite: bool = False,
distribution_name=None,
sample_file_name=None,
):
"""
Copies a sample configuration file from an available plugin.
return configuration_file_path
:param configuration_file_path: the path of file to be written
:param overwrite: if True, the file will be written, even if it already exists
:param distribution_name: the name of the installed package that provides the sample
configuration file (can be omitted if only one plugin is available)
:param sample_file_name: the name of the sample configuration file (can be omitted if
the plugin provides only one configuration file)
:return: path of generated file
:raise FastPathExistsError: if overwrite==False and configuration_file_path already exists
"""

return _generate_user_file(
user_file_type=UserFileType.CONFIGURATION,
user_file_path=configuration_file_path,
overwrite=overwrite,
distribution_name=distribution_name,
sample_user_file_name=sample_file_name,
)


def generate_source_data_file(
source_data_file_path: str,
overwrite: bool = False,
distribution_name=None,
sample_file_name=None,
):
"""
Copies a sample source data file from an available plugin.
:param source_data_file_path: the path of file to be written
:param overwrite: if True, the file will be written, even if it already exists
:param distribution_name: the name of the installed package that provides the sample
source data file (can be omitted if only one plugin is available)
:param sample_file_name: the name of the sample source data file (can be omitted if
the plugin provides only one source data file)
:return: path of generated file
:raise FastPathExistsError: if overwrite==False and source_file_path already exists
"""

return _generate_user_file(
user_file_type=UserFileType.SOURCE_DATA,
user_file_path=source_data_file_path,
overwrite=overwrite,
distribution_name=distribution_name,
sample_user_file_name=sample_file_name,
)


def generate_inputs(
configuration_file_path: str,
source_path: str = None,
source_path_schema="native",
source_data_path: str = None,
source_data_path_schema="native",
overwrite: bool = False,
) -> str:
"""
Generates input file for the problem specified in configuration_file_path.
:param configuration_file_path: where the path of input file to write is set
:param source_path: path of file data will be taken from
:param source_path_schema: set to 'legacy' if the source file come from legacy FAST
:param source_data_path: path of source data file data will be taken from
:param source_data_path_schema: set to 'legacy' if the source file come from legacy FAST
:param overwrite: if True, file will be written even if one already exists
:return: path of generated file
:raise FastPathExistsError: if overwrite==False and configuration_file_path already exists
Expand All @@ -225,10 +317,10 @@ def generate_inputs(
input_file_path,
)

if source_path_schema == "legacy":
conf.write_needed_inputs(source_path, VariableLegacy1XmlFormatter())
if source_data_path_schema == "legacy":
conf.write_needed_inputs(source_data_path, VariableLegacy1XmlFormatter())
else:
conf.write_needed_inputs(source_path)
conf.write_needed_inputs(source_data_path)

_LOGGER.info("Problem inputs written in %s", input_file_path)
return input_file_path
Expand Down
54 changes: 49 additions & 5 deletions src/fastoad/cmd/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@
FastSeveralDistPluginsError,
FastUnknownConfigurationFileError,
FastUnknownDistPluginError,
FastNoAvailableSourceDataFileError,
FastSeveralSourceDataFilesError,
FastUnknownSourceDataFileError,
)
from . import api

Expand Down Expand Up @@ -90,14 +93,55 @@ def gen_conf(conf_file, from_package, source, force):
click.echo(exc.args[0])


@fast_oad.command(name="gen_source_data_file")
@click.argument("source_data_file", nargs=1)
@click.option(
"-p",
"--from_package",
nargs=1,
help="Name of installed package that provides the sample source data file.",
)
@click.option("-s", "--source", nargs=1, help="Name of original source data file.")
@overwrite_option
def gen_source_data_file(source_data_file, from_package, source, force):
"""
Generate a sample source data file with given argument as name.
Option "--from_package" has to be used if several FAST-OAD plugins are available.
Option "--source" has to be used if the targeted plugin provides several sample
source data files.
Use "fastoad plugin_info" to get information about available plugins and sample
configuration files.
"""
try:
manage_overwrite(
api.generate_source_data_file,
source_data_file_path=source_data_file,
overwrite=force,
distribution_name=from_package,
sample_file_name=source,
)
except (
FastNoDistPluginError,
FastSeveralDistPluginsError,
FastUnknownDistPluginError,
FastSeveralSourceDataFilesError,
FastUnknownSourceDataFileError,
FastNoAvailableSourceDataFileError,
) as exc:
click.echo(exc.args[0])


@fast_oad.command(name="gen_inputs")
@click.argument("conf_file", nargs=1)
@click.argument("source_file", nargs=1, required=False)
@click.argument("source_data_file", nargs=1, required=False)
@overwrite_option
@click.option(
"--legacy", is_flag=True, help="To be used if the source XML file is in legacy format."
"--legacy", is_flag=True, help="To be used if the source data XML file is in legacy format."
)
def gen_inputs(conf_file, source_file, force, legacy):
def gen_inputs(conf_file, source_data_file, force, legacy):
"""
Generate the input file (specified in the configuration file) with needed variables.
Expand All @@ -120,8 +164,8 @@ def gen_inputs(conf_file, source_file, force, legacy):
manage_overwrite(
api.generate_inputs,
configuration_file_path=conf_file,
source_path=source_file,
source_path_schema=schema,
source_data_path=source_data_file,
source_data_path_schema=schema,
overwrite=force,
)

Expand Down

0 comments on commit 832fc57

Please sign in to comment.