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

Generalized macOS stdout handler in Config #137

Draft
wants to merge 5 commits into
base: dev
Choose a base branch
from
Draft
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
118 changes: 100 additions & 18 deletions biosimulators_utils/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,25 @@
:License: MIT
"""

from .omex_meta.data_model import OmexMetadataInputFormat, OmexMetadataOutputFormat, OmexMetadataSchema
from .report.data_model import ReportFormat # noqa: F401
from .viz.data_model import VizFormat # noqa: F401
from kisao import AlgorithmSubstitutionPolicy # noqa: F401
import appdirs
import enum
import os
import platform
from typing import Dict, List, Union
from kisao import AlgorithmSubstitutionPolicy # noqa: F401
from biosimulators_utils.log.data_model import StandardOutputErrorCapturerLevel
from biosimulators_utils.omex_meta.data_model import OmexMetadataInputFormat, OmexMetadataOutputFormat, OmexMetadataSchema
from biosimulators_utils.report.data_model import ReportFormat # noqa: F401
from biosimulators_utils.viz.data_model import VizFormat # noqa: F401
import appdirs

__all__ = ['Config', 'get_config', 'Colors', 'get_app_dirs']

CURRENT_PLATFORM = platform.system()
try:
assert CURRENT_PLATFORM == "Darwin"
Copy link
Contributor

Choose a reason for hiding this comment

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

so this will catch all Mac OS? Have you also tested the bug in non-Mac OS? Why is it that python is connected to Mac and all others use c?

DEFAULT_STDOUT_LEVEL = StandardOutputErrorCapturerLevel.python
except AssertionError as e:
DEFAULT_STDOUT_LEVEL = StandardOutputErrorCapturerLevel.c
DEFAULT_OMEX_METADATA_INPUT_FORMAT = OmexMetadataInputFormat.rdfxml
DEFAULT_OMEX_METADATA_OUTPUT_FORMAT = OmexMetadataOutputFormat.rdfxml_abbrev
DEFAULT_OMEX_METADATA_SCHEMA = OmexMetadataSchema.biosimulations
Expand Down Expand Up @@ -65,6 +74,7 @@ class Config(object):
BIOSIMULATIONS_API_AUDIENCE (:obj:`str`): audience for the BioSimulations API
VERBOSE (:obj:`bool`): whether to display the detailed output of the execution of each task
DEBUG (:obj:`bool`): whether to raise exceptions rather than capturing them
STDOUT_LEVEL (:obj:`StandardOutputErrorCapturerLevel`): level at which to log the output
"""

def __init__(self,
Expand All @@ -82,8 +92,8 @@ def __init__(self,
COLLECT_COMBINE_ARCHIVE_RESULTS=False,
COLLECT_SED_DOCUMENT_RESULTS=False,
SAVE_PLOT_DATA=True,
REPORT_FORMATS=[ReportFormat.h5],
VIZ_FORMATS=[VizFormat.pdf],
REPORT_FORMATS=None,
VIZ_FORMATS=None,
H5_REPORTS_PATH=DEFAULT_H5_REPORTS_PATH,
REPORTS_PATH=DEFAULT_REPORTS_PATH,
PLOTS_PATH=DEFAULT_PLOTS_PATH,
Expand All @@ -96,7 +106,9 @@ def __init__(self,
BIOSIMULATIONS_API_AUTH_ENDPOINT=DEFAULT_BIOSIMULATIONS_API_AUTH_ENDPOINT,
BIOSIMULATIONS_API_AUDIENCE=DEFAULT_BIOSIMULATIONS_API_AUDIENCE,
VERBOSE=False,
DEBUG=False):
DEBUG=False,
STDOUT_LEVEL=DEFAULT_STDOUT_LEVEL,
**CUSTOM_SETTINGS):
Copy link
Contributor

Choose a reason for hiding this comment

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

using the ** I see. kind of dangerous that anything can come in here and will be applied to the settings. Do you have examples of what kind of kwargs might be used?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a part of my original implementation in an idea that I had. As you can see starting at line 181, these **kwargs become unpacked in the private method __getcustomsettings() and returned as a dict in the attribute CUSTOM_SETTINGS. I realize the danger of arbitrarily using unpacking, but I have funneled it into a collection.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Devising some presentable use cases now to plead my case, hah.

"""
Args:
OMEX_METADATA_INPUT_FORMAT (:obj:`OmexMetadataInputFormat`, optional): format to validate OMEX Metadata files against
Expand All @@ -117,7 +129,7 @@ def __init__(self,
COLLECT_SED_DOCUMENT_RESULTS (:obj:`bool`, optional): whether to assemble an in memory data structure with all of the
simulation results of SED documents
SAVE_PLOT_DATA (:obj:`bool`, optional): whether to save data for plots alongside data for reports in CSV/HDF5 files
REPORT_FORMATS (:obj:`list` of :obj:`str`, optional): default formats to generate reports in
REPORT_FORMATS (:obj:`list` of :obj:`str`, optional): default formats to generate reports in-->defaults to 'csv'
Copy link
Contributor

Choose a reason for hiding this comment

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

This PR sets the default to None

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I originally was generating the value of REPORT_FORMATS to have a default constructor setting of [ReportFormat.csv] (formats are of type(enum.Enum)), or even [ReportFormat.h5]. Our good friend SonarCloud complained about this definition, which makes sense as the definition is also performing a very basic operation other than just instantiation. This doc string annotation was from when I had it in the definition and must have missed it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

After review, that doc string is actually as I intended it to be. Following the logic of the constructor, if there is no value for the REPORT_FORMATS attribute upon instantiation, it defaults to 'csv'. As per the constructor: self.REPORT_FORMATS = REPORT_FORMATS or [ReportFormat.csv], which evaluates roughly as:

if not REPORT_FORMATS:
REPORT_FORMATS = [ReportFormat.csv]

VIZ_FORMATS (:obj:`list` of :obj:`str`, optional): default formats to generate plots in
H5_REPORTS_PATH (:obj:`str`, optional): path to save reports in HDF5 format relative to base output directory
REPORTS_PATH (:obj:`str`, optional): path to save zip archive of reports relative to base output directory
Expand All @@ -132,6 +144,7 @@ def __init__(self,
BIOSIMULATIONS_API_AUDIENCE (:obj:`str`, optional): audience for the BioSimulations API
VERBOSE (:obj:`bool`, optional): whether to display the detailed output of the execution of each task
DEBUG (:obj:`bool`, optional): whether to raise exceptions rather than capturing them
STDOUT_LEVEL (:obj:`StandardOutputErrorCapturerLevel`): level at which to log the output
"""
self.OMEX_METADATA_INPUT_FORMAT = OMEX_METADATA_INPUT_FORMAT
self.OMEX_METADATA_OUTPUT_FORMAT = OMEX_METADATA_OUTPUT_FORMAT
Expand All @@ -147,8 +160,8 @@ def __init__(self,
self.COLLECT_COMBINE_ARCHIVE_RESULTS = COLLECT_COMBINE_ARCHIVE_RESULTS
self.COLLECT_SED_DOCUMENT_RESULTS = COLLECT_SED_DOCUMENT_RESULTS
self.SAVE_PLOT_DATA = SAVE_PLOT_DATA
self.REPORT_FORMATS = REPORT_FORMATS
self.VIZ_FORMATS = VIZ_FORMATS
self.REPORT_FORMATS = REPORT_FORMATS or [ReportFormat.csv]
Copy link
Contributor

Choose a reason for hiding this comment

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

Hah, I saw this and thought it was an actual csv file rather than a csv attribute of ReportFormat.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You know, I did at first as well! As I mentioned, I moved the logic for this inside the actual constructor as per SonarCloud.

self.VIZ_FORMATS = VIZ_FORMATS or [VizFormat.pdf]
self.H5_REPORTS_PATH = H5_REPORTS_PATH
self.REPORTS_PATH = REPORTS_PATH
self.PLOTS_PATH = PLOTS_PATH
Expand All @@ -162,23 +175,65 @@ def __init__(self,
self.BIOSIMULATIONS_API_AUDIENCE = BIOSIMULATIONS_API_AUDIENCE
self.VERBOSE = VERBOSE
self.DEBUG = DEBUG
self.STDOUT_LEVEL = STDOUT_LEVEL
self.CUSTOM_SETTINGS = self.__getcustomsettings(CUSTOM_SETTINGS)

def __getcustomsettings(self, settings: Dict = None):
for key in settings.keys():
setattr(self, key, settings[key])
return self


def get_config():
""" Get the configuration

def get_config(report_format: str = None,
viz_format: str = None,
acceptable_report_formats: Union[List[str], ReportFormat] = ReportFormat,
acceptable_viz_formats: Union[List[str], VizFormat] = VizFormat,
*_default_format_settings):
""" Get the configuration based on specified optional settings. Handles sets default values for
`report_format` and `viz_format` if these respective variables are empty.

Args:
:str:`report_format`: output format for reports. Defaults to `None`

:str:`viz_format`: output format for visualizations. Defaults to `None`

:Union:`acceptable_report_formats`: acceptable formats for output report data. Defaults to `biosimulators_utils.report.data_model.ReportFormat`

:Union:`acceptable_viz_formats`: acceptable formats for output viz data. Defaults to `biosimulators_utils.viz.data_model.VizFormat`

Returns:
:obj:`Config`: configuration
"""
report_formats = os.environ.get('REPORT_FORMATS', 'h5').strip()

if not _default_format_settings: #get
_default_format_settings = ('csv', 'pdf') #set

user_report_format = verify_formats(
report_format,
acceptable_report_formats,
_default_format_settings[0]
)

user_viz_format = verify_formats(
viz_format,
acceptable_viz_formats,
_default_format_settings[1]
)

report_formats = os.environ.get('REPORT_FORMATS', user_report_format).strip()

if report_formats:
report_formats = [ReportFormat(format.strip().lower()) for format in report_formats.split(',')]
report_formats = [
ReportFormat(format.strip().lower()) for format in report_formats.split(',')
]
else:
report_formats = []

viz_formats = os.environ.get('VIZ_FORMATS', 'pdf').strip()
viz_formats = os.environ.get('VIZ_FORMATS', user_viz_format).strip()
if viz_formats:
viz_formats = [VizFormat(format.strip().lower()) for format in viz_formats.split(',')]
viz_formats = [
VizFormat(format.strip().lower()) for format in viz_formats.split(',')
]
else:
viz_formats = []

Expand Down Expand Up @@ -216,6 +271,7 @@ def get_config():
BIOSIMULATIONS_API_AUDIENCE=os.environ.get('BIOSIMULATIONS_API_AUDIENCE', DEFAULT_BIOSIMULATIONS_API_AUDIENCE),
VERBOSE=os.environ.get('VERBOSE', '1').lower() in ['1', 'true'],
DEBUG=os.environ.get('DEBUG', '0').lower() in ['1', 'true'],
STDOUT_LEVEL=os.environ.get('STDOUT_LEVEL', DEFAULT_STDOUT_LEVEL)
)


Expand Down Expand Up @@ -244,3 +300,29 @@ def get_app_dirs():
:obj:`appdirs.AppDirs`: application directories
"""
return appdirs.AppDirs("BioSimulatorsUtils", "BioSimulatorsTeam")


def verify_formats(format_type: str, acceptable_format: enum.Enum, default: str):
def verify_format(format_type, acceptable_format):
acceptable_formats = _get_acceptable_formats(acceptable_format)
if format_type not in acceptable_formats:
print(
f'''Sorry, you must enter one of the following acceptable formats:
{acceptable_formats}. \nSetting to default format: {default}'''
)
return False
else:
return True

return default if not verify_format(format_type, acceptable_format)\
else format_type


def acceptable_viz_formats():
return _get_acceptable_formats(VizFormat)

def acceptable_report_formats():
return _get_acceptable_formats(ReportFormat)

def _get_acceptable_formats(acceptable_formats: enum.Enum):
return [v.value for v in acceptable_formats]