#####################################################################
# BioSimSpace: Making biomolecular simulation a breeze!
#
# Copyright: 2017-2024
#
# Authors: Lester Hedges <lester.hedges@gmail.com>
#
# BioSimSpace is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# BioSimSpace is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with BioSimSpace. If not, see <http://www.gnu.org/licenses/>.
#####################################################################
"""
Functionality for defining and validating BioSimSpace input and output requirements.
Author: Lester Hedges <lester.hedges@gmail.com>.
"""
__author__ = "Lester Hedges"
__email__ = "lester.hedges@gmail.com"
__all__ = [
"Boolean",
"Integer",
"Float",
"String", # Regular types.
"File",
"FileSet", # File types.
"Length",
"Area",
"Volume", # Length types.
"Angle",
"Charge",
"Energy",
"Pressure",
"Temperature",
"Time",
]
import bz2 as _bz2
import copy as _copy
import gzip as _gzip
import os as _os
import re as _re
import shutil as _shutil
import tarfile as _tarfile
import zipfile as _zipfile
from .. import Types as _Types
class Requirement:
"""Base class for BioSimSpace Node requirements."""
# Set the argparse argument type.
_arg_type = None
# Default to single arguments.
_is_multi = False
def __init__(
self,
help=None,
default=None,
unit=None,
minimum=None,
maximum=None,
allowed=None,
optional=False,
):
"""
Constructor.
Parameters
----------
help : str
The help string.
default :
The default value.
unit : str
The unit.
minimum :
The minimum allowed value.
maximum :
The maximum allowed value.
allowed : list
A list of allowed values.
optional : bool
Whether the requirement is optional.
"""
# Don't allow user to create an instance of this base class.
if type(self) is Requirement:
raise Exception("<Requirement> must be subclassed.")
# Required keyword arguments.
if help is None:
raise ValueError("Missing 'help' keyword argument!")
elif not isinstance(help, str):
raise TypeError("'help' keyword argument must be of type 'str'")
else:
self._help = help
# Optional keywords arguments.
if not isinstance(optional, bool):
raise TypeError("'optional' keyword argument must be of type 'bool'")
# Set defaults
self._value = None
self._default = None
self._unit = unit
self._min = None
self._max = None
self._allowed = None
self._is_optional = optional
# Set the minimum value.
if minimum is not None:
self._min = self._validate(minimum)
# Set the maximum value.
if maximum is not None:
self._max = self._validate(maximum)
# Min and max.
if minimum is not None and maximum is not None:
if self._max < self._min:
raise ValueError(
"The maximum value '%s' is less than the minimum '%s'"
% (maximum, minimum)
)
# Convert tuple to list.
if isinstance(allowed, tuple):
allowed = list(allowed)
# Set the allowed values.
if allowed is not None:
if not isinstance(allowed, list):
allowed = [allowed]
self._allowed = [self._validate(x) for x in allowed]
# Conflicting requirements.
if self._min is not None or self._max is not None:
raise ValueError(
"Conflicting requirement: cannot have allowed values and min/max."
)
# Set the default value.
if default is not None:
# Make sure the default is the right type.
self._default = self._validate(default)
# Make sure the default satisfies the constraints.
self._validate_default()
# Flag the the requirement is optional.
self._is_optional = True
def setValue(self, value, name=None):
"""
Validate and set the value.
Parameters
----------
value :
The value of the input requirement.
name : str
The name of the requirement.
"""
if value is None and not self._is_optional:
if isinstance(name, str):
raise ValueError("Value is unset for requirement '%s'!" % name)
else:
raise ValueError("Value is unset!")
# Validate the value.
value = self._validate(value)
# Now check value against any constraints.
# Minimum.
if self._min is not None and value < self._min:
raise ValueError(
"The value (%s) is less than the allowed "
"minimum (%s)" % (value, self._min)
)
# Maximum.
if self._max is not None and value > self._max:
raise ValueError(
"The value (%s) is greater than the allowed "
"maximum (%s)" % (value, self._max)
)
# Allowed values.
if self._allowed is not None and value not in self._allowed:
# For String requirements, strip whitespace and ignore case.
if isinstance(self, String):
new_value = value.replace(" ", "").upper()
allowed = [x.replace(" ", "").upper() for x in self._allowed]
# If we find a match, then set to the unmodified allowed value
# at the matching index.
if new_value in allowed:
value = self._allowed[allowed.index(new_value)]
else:
raise ValueError(
"The value (%s) is not in the list of allowed values: "
"%s" % (value, str(self._allowed))
)
else:
raise ValueError(
"The value (%s) is not in the list of allowed values: "
"%s" % (value, str(self._allowed))
)
# All is okay. Set the value.
self._value = value
def getValue(self):
"""Return the value."""
return self._value
def getDefault(self):
"""Return the default value."""
return self._default
def getUnit(self):
"""
Return the unit.
Returns
-------
unit : str
The unit associated with the requirement.
"""
return self._unit
def getHelp(self):
"""
Return the documentation string.
Returns
-------
help : str
The help string.
"""
return self._help
def isMulti(self):
"""
Whether the requirement has multiple values.
Returns
-------
is_multi : bool
Whether the requirement has multiple values.
"""
return self._is_multi
def isOptional(self):
"""
Whether the requirement is optional.
Returns
-------
is_optional : bool
Whether the requirement is optional.
"""
return self._is_optional
def getArgType(self):
"""
The command-line argument type.
Returns
-------
arg_type : bool, int, float, str
The command-line argument type.
"""
return self._arg_type
def getMin(self):
"""Return the minimum allowed value."""
return self._min
def getMax(self):
"""Return the maximum allowed value."""
return self._max
def getAllowedValues(self):
"""
Return the allowed values.
Returns
-------
allowed : list
The list of allowed values that the requirement can take.
"""
return self._allowed
def _validate_default(self):
"""Validate the default value."""
if self._min is not None and self._default < self._min:
raise ValueError(
"The default '%s' is less than the minimum "
"allowed value '%s'" % (self._default, self._min)
)
if self._max is not None and self._default > self._max:
raise ValueError(
"The default '%s' is greater than the maximum "
"allowed value '%s'" % (self._default, self._max)
)
if self._allowed is not None and self._default not in self._allowed:
raise ValueError(
"The default '%s' is not one of the allowed values %s"
% (self._default, str(self._allowed))
)
[docs]
class Boolean(Requirement):
"""A boolean requirement.
Example
-------
Create a boolean flag with a default of False.
>>> import BioSimSpace as BSS
>>> flag = BSS.Gateway.Boolean(help="A boolean flag", default=False)
"""
# Set the argparse argument type.
_arg_type = bool
[docs]
def __init__(self, help=None, default=None):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : bool
The default value.
"""
# Call the base class constructor.
super().__init__(help=help, default=default)
def _validate(self, value):
"""Validate the value."""
if isinstance(value, bool):
return value
else:
raise TypeError("The value should be of type 'bool'.")
[docs]
class Integer(Requirement):
"""An integer requirement.
Examples
--------
Create an integer requirement with an allowed range and no default.
>>> import BioSimSpace as BSS
>>> my_int = BSS.Gateway.Integer(help="An integer requirement.", minimum=0, maximum=10)
Create an integer requirement with a given set of allowed values.
>>> import BioSimSpace as BSS
>>> my_int = BSS.Gateway.Integer(help="An integer requirement.", allowed=[1,2,3,4,5])
Create an integer requirement with a maximum value of 10 and default of 3.
>>> import BioSimSpace as BSS
>>> my_int = BSS.Gateway.Integer(help="An integer requirement.", default=3, maximum=10)
"""
# Set the argparse argument type.
_arg_type = int
[docs]
def __init__(
self, help=None, default=None, minimum=None, maximum=None, allowed=None
):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : int
The default value.
minimum : int
The minimum allowed value.
maximum : int
The maximum allowed value.
allowed : [int]
A list of allowed values.
"""
# Call the base class constructor.
super().__init__(
help=help,
default=default,
minimum=minimum,
maximum=maximum,
allowed=allowed,
)
def _validate(self, value):
"""Validate that the value is of the correct type."""
if type(value) is int:
return value
else:
raise TypeError("The value should be of type 'int'.")
[docs]
class Float(Requirement):
"""A floating point requirement.
Examples
--------
Create a float requirement with an allowed range and no default.
>>> import BioSimSpace as BSS
>>> my_float = BSS.Gateway.Float(help="A float requirement.", minimum=-13.2, maximum=27.3)
Create a float requirement with a given set of allowed values.
>>> import BioSimSpace as BSS
>>> my_float = BSS.Gateway.Float(help="A float requirement.", allowed=[1.0,3.5,12.8])
Create a float requirement with a maximum value of 57.3 and default of 18.2.
>>> import BioSimSpace as BSS
>>> my_float = BSS.Gateway.Float(help="A float requirement.", default=18.2, maximum=57.3)
"""
# Set the argparse argument type.
_arg_type = float
[docs]
def __init__(
self, help=None, default=None, minimum=None, maximum=None, allowed=None
):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : float
The default value.
minimum : float
The minimum allowed value.
maximum : float
The maximum allowed value.
allowed : [float]
A list of allowed values.
"""
# Call the base class constructor.
super().__init__(
help=help,
default=default,
minimum=minimum,
maximum=maximum,
allowed=allowed,
)
def _validate(self, value):
"""Validate that the value is of the correct type."""
if isinstance(value, float):
return value
elif type(value) is int:
return float(value)
else:
raise TypeError("The value should be of type 'float' or 'int'.")
[docs]
class String(Requirement):
"""A string requirement.
Examples
--------
Create a string requirement with a default value.
>>> import BioSimSpace as BSS
>>> my_string = BSS.Gateway.String(help="A string requirement.", default="dog")
Create a string requirement with a list of allowed values and a default of "cat".
>>> import BioSimSpace as BSS
>>> my_string = BSS.Gateway.String(help="A string requirement.", allowed=["cat", "dog", "fish"], default="cat")
"""
# Set the argparse argument type.
_arg_type = str
[docs]
def __init__(self, help=None, default=None, allowed=None):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : str
The default value.
allowed : [str]
A list of allowed values.
"""
# Call the base class constructor.
super().__init__(help=help, default=default, allowed=allowed)
def _validate(self, value):
"""Validate that the value is of the correct type."""
if isinstance(value, str):
return value
else:
raise TypeError("The value should be of type 'str'")
[docs]
class File(Requirement):
"""A file requirement.
Example
-------
Create an optional file requirement.
>>> import BioSimSpace as BSS
>>> my_file = BSS.Gateway.File(help="A file requirement.", optional=True)
"""
# Set the argparse argument type.
_arg_type = str
[docs]
def __init__(self, help=None, optional=False):
"""
Constructor.
Parameters
----------
help : str
The help string.
optional : bool
Whether the file is optional.
"""
# Call the base class constructor.
super().__init__(help=help, optional=optional)
def _validate(self, value):
"""Validate that the value is of the correct type."""
# Handle optional requirement.
if self._is_optional and value is None:
return None
# Check the type.
if isinstance(value, str):
file = _unarchive(value)
if file is None:
file = value
elif isinstance(file, (list, tuple)):
raise ValueError(
"The archive contains multiple files: use a FileSet instead!"
)
else:
raise TypeError("The value should be of type 'str'")
# Make sure the file exists.
if not _os.path.isfile(file):
raise IOError("File doesn't exist: '%s'" % file)
else:
return file
[docs]
class FileSet(Requirement):
"""A file requirement.
Example
-------
Create a file set requirement.
>>> import BioSimSpace as BSS
>>> my_files = BSS.Gateway.FileSet(help="A file set requirement.")
"""
# Set the argparse argument type.
_arg_type = str
# Multiple files can be passed.
_is_multi = True
[docs]
def __init__(self, help=None, optional=False):
"""
Constructor.
Parameters
----------
help : str
The help string.
optional : bool
Whether the file set is optional.
"""
# Call the base class constructor.
super().__init__(help=help, optional=optional)
[docs]
def getValue(self):
"""
Return the value.
Returns
-------
value : [str]
A list of the files associated with this requirement.
"""
if self._value is None:
return None
else:
return self._value.copy()
def _validate(self, value):
"""Validate that the value is of the correct type."""
# Handle optional requirement.
if self._is_optional and value is None:
return None
# Handle single strings.
if isinstance(value, str):
value = [value]
# The user can pass a list of compressed files so we need to keep
# track of the names of the uncompressed files.
uncompressed_files = []
# We should receive a list of strings.
if isinstance(value, (list, tuple)):
# A single file was passed.
if len(value) == 1:
# Remove whitespace and split on commas.
value = _re.sub(r"\s+", "", value[0]).split(",")
# Loop over all strings.
for file in value:
# Check the types.
if not isinstance(file, str):
raise TypeError("The value should be of type 'str'")
# Check whether this is an archive.
files = _unarchive(file)
if files is not None:
if isinstance(files, list):
uncompressed_files += files
else:
uncompressed_files.append(files)
else:
# Make sure the file exists.
if not _os.path.isfile(file):
raise IOError("File doesn't exist: '%s'" % file)
if len(uncompressed_files) > 0:
return uncompressed_files
else:
return value
[docs]
class Length(Requirement):
"""A length requirement.
Examples
--------
Create a length requirement with a default of 10 Angstrom.
>>> import BioSimSpace as BSS
>>> my_length = BSS.Gateway.Length(help="A length requirement", default=10, unit="angstrom")
The same, but explicitly passing a :class:`Length <BioSimSpace.Types.Length>`
for the default.
>>> import BioSimSpace as BSS
>>> my_length = BSS.Gateway.Length(help="A length requirement",
... default=10*BSS.Units.Length.angstrom)
Create a length requirement with a default of 10 Angstrom and a maximum
of 50 nanometers. Note that the unit is taken from the default value.
>>> import BioSimSpace as BSS
>>> my_length = BSS.Gateway.Length(help="A length requirement",
... default=10*BSS.Units.Length.angstrom,
... maximum=50*BSS.Units.Length.nanometer)
"""
# Set the argparse argument type.
_arg_type = str
[docs]
def __init__(
self,
help=None,
default=None,
unit=None,
minimum=None,
maximum=None,
allowed=None,
):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : :class:`Length <BioSimSpace.Types.Length>`
The default value.
unit : str
The unit.
minimum : :class:`Length <BioSimSpace.Types.Length>`
The minimum allowed value.
maximum : :class:`Length <BioSimSpace.Types.Length>`
The maximum allowed value.
allowed : [:class:`Length <BioSimSpace.Types.Length>`]
A list of allowed values.
"""
# Validate the unit.
if unit is not None:
length = _Types.Length("1 %s" % unit)
self._unit = length.unit()
self._print_unit = length._print_format[length.unit()]
else:
try:
self._unit = default.unit()
except:
raise ValueError("No unit or default value has been specified!")
# Call the base class constructor.
super().__init__(
help=help,
default=default,
unit=self._unit,
minimum=minimum,
maximum=maximum,
allowed=allowed,
)
[docs]
def getValue(self):
"""
Return the value.
Returns
-------
value : :class:`Length <BioSimSpace.Types.Length>`
The value of the requirement.
"""
if self._value is None:
return None
else:
return _copy.deepcopy(self._value)
def _validate(self, value):
"""Validate that the value is of the correct type."""
if isinstance(value, _Types.Length):
return value._convert_to(self._unit)
else:
# Extract the value and unit from the argument string.
value, unit = _validate_unit_requirement(value, "length")
if unit is None:
return _Types.Length(value, self._unit)
else:
return _Types.Length(value, unit)._convert_to(self._unit)
[docs]
class Area(Requirement):
"""An area requirement.
Examples
--------
Create an area requirement with a default of 10 square Angstrom.
>>> import BioSimSpace as BSS
>>> my_area = BSS.Gateway.Area(help="An area requirement", default=10, unit="angstrom2")
The same, but explicitly passing a :class:`Area <BioSimSpace.Types.Area>`
for the default.
>>> import BioSimSpace as BSS
>>> my_area = BSS.Gateway.Area(help="An area requirement", default=10*BSS.Units.Area.angstrom2)
Create an area requirement with a default of 10 square Angstrom and a maximum
of 50 square nanometers. Note that the unit is taken from the default value.
>>> import BioSimSpace as BSS
>>> my_area = BSS.Gateway.Area(help="An area requirement",
... default=100*BSS.Units.Area.angstrom2,
... maximum=50*BSS.Units.Area.nanometer2)
"""
# Set the argparse argument type.
_arg_type = str
[docs]
def __init__(
self,
help=None,
default=None,
unit=None,
minimum=None,
maximum=None,
allowed=None,
):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : :class:`Area <BioSimSpace.Types.Area>`
The default value.
unit : str
The unit.
minimum : :class:`Area <BioSimSpace.Types.Area>`
The minimum allowed value.
maximum : :class:`Area <BioSimSpace.Types.Area>`
The maximum allowed value.
allowed : [:class:`Area <BioSimSpace.Types.Area>`]
A list of allowed values.
"""
# Validate the unit.
if unit is not None:
area = _Types.Area("1 %s" % unit)
self._unit = area.unit()
self._print_unit = area._print_format[area.unit()]
else:
try:
self._unit = default.unit()
except:
raise ValueError("No unit or default value has been specified!")
# Call the base class constructor.
super().__init__(
help=help,
default=default,
unit=self._unit,
minimum=minimum,
maximum=maximum,
allowed=allowed,
)
[docs]
def getValue(self):
"""
Return the value.
Returns
-------
value : :class:`Area <BioSimSpace.Types.Area>`
The value of the requirement.
"""
if self._value is None:
return None
else:
return _copy.deepcopy(self._value)
def _validate(self, value):
"""Validate that the value is of the correct type."""
if isinstance(value, _Types.Area):
return value._convert_to(self._unit)
else:
# Extract the value and unit from the argument string.
value, unit = _validate_unit_requirement(value, "area")
if unit is None:
return _Types.Area(value, self._unit)
else:
return _Types.Area(value, unit)._convert_to(self._unit)
[docs]
class Volume(Requirement):
"""A volume requirement.
Examples
--------
Create a volume requirement with a default of 10 cubed Angstrom.
>>> import BioSimSpace as BSS
>>> my_volume = BSS.Gateway.Volume(help="A volume requirement", default=10, unit="angstrom3")
The same, but explicitly passing a :class:`Volume <BioSimSpace.Types.Volume>`
for the default.
>>> import BioSimSpace as BSS
>>> my_volume = BSS.Gateway.Volume(help="A volume requirement", default=10*BSS.Units.Volume.angstrom3)
Create a volume requirement with a default of 10 cubed Angstrom and a maximum
of 50 cubed nanometers. Note that the unit is taken from the default value.
>>> import BioSimSpace as BSS
>>> my_volume = BSS.Gateway.Volume(help="A volume requirement",
... default=10*BSS.Units.Volume.angstrom3,
... maximum=50*BSS.Units.Volume.nanometer3)
"""
# Set the argparse argument type.
_arg_type = str
[docs]
def __init__(
self,
help=None,
default=None,
unit=None,
minimum=None,
maximum=None,
allowed=None,
):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : :class:`Volume <BioSimSpace.Types.Volume>`
The default value.
unit : str
The unit.
minimum : :class:`Volume <BioSimSpace.Types.Volume>`
The minimum allowed value.
maximum : :class:`Volume <BioSimSpace.Types.Volume>`
The maximum allowed value.
allowed : [:class:`Volume <BioSimSpace.Types.Volume>`]
A list of allowed values.
"""
# Validate the unit.
if unit is not None:
volume = _Types.Volume("1 %s" % unit)
self._unit = volume.unit()
self._print_unit = volume._print_format[volume.unit()]
else:
try:
self._unit = default.unit()
except:
raise ValueError("No unit or default value has been specified!")
# Call the base class constructor.
super().__init__(
help=help,
default=default,
unit=self._unit,
minimum=minimum,
maximum=maximum,
allowed=allowed,
)
[docs]
def getValue(self):
"""
Return the value.
Returns
-------
value : :class:`Volume <BioSimSpace.Types.Volume>`
The value of the requirement.
"""
if self._value is None:
return None
else:
return _copy.deepcopy(self._value)
def _validate(self, value):
"""Validate that the value is of the correct type."""
if isinstance(value, _Types.Volume):
return value._convert_to(self._unit)
else:
# Extract the value and unit from the argument string.
value, unit = _validate_unit_requirement(value, "volume")
if unit is None:
return _Types.Volume(value, self._unit)
else:
return _Types.Volume(value, unit)._convert_to(self._unit)
class Angle(Requirement):
"""An angle requirement.
Examples
--------
Create an angle requirement with a default of 3.14 radians.
>>> import BioSimSpace as BSS
>>> my_angle = BSS.Gateway.Angle(help="An angle requirement", default=3.14, unit="radian")
The same, but explicitly passing a :class:`Angle <BioSimSpace.Types.Angle>`
for the default.
>>> import BioSimSpace as BSS
>>> my_angle = BSS.Gateway.Angle(help="An angle requirement", default=3.14*BSS.Units.Angle.radian)
Create an angle requirement with a default of 3.14 radian and a maximum
of 360 degrees. Note that the unit is taken from the default value.
>>> import BioSimSpace as BSS
>>> my_angle = BSS.Gateway.Angle(help="An angle requirement",
... default=3.14*BSS.Units.Angle.radian,
... maximum=360*BSS.Units.Angle.degree)
"""
# Set the argparse argument type.
_arg_type = str
def __init__(
self,
help=None,
default=None,
unit=None,
minimum=None,
maximum=None,
allowed=None,
):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : :class:`Angle <BioSimSpace.Types.Angle>`
The default value.
unit : str
The unit.
minimum : :class:`Angle <BioSimSpace.Types.Angle>`
The minimum allowed value.
maximum : :class:`Angle <BioSimSpace.Types.Angle>`
The maximum allowed value.
allowed : [:class:`Angle <BioSimSpace.Types.Angle>`]
A list of allowed values.
"""
# Validate the unit.
if unit is not None:
angle = _Types.Angle("1 %s" % unit)
self._unit = angle.unit()
self._print_unit = angle._print_format[angle.unit()]
else:
try:
self._unit = default.unit()
except:
raise ValueError("No unit or default value has been specified!")
# Call the base class constructor.
super().__init__(
help=help,
default=default,
unit=self._unit,
minimum=minimum,
maximum=maximum,
allowed=allowed,
)
def getValue(self):
"""
Return the value.
Returns
-------
value : :class:`Angle <BioSimSpace.Types.Angle>`
The value of the requirement.
"""
if self._value is None:
return None
else:
return _copy.deepcopy(self._value)
def _validate(self, value):
"""Validate that the value is of the correct type."""
if isinstance(value, _Types.Angle):
return value._convert_to(self._unit)
else:
# Extract the value and unit from the argument string.
value, unit = _validate_unit_requirement(value, "angle")
if unit is None:
return _Types.Angle(value, self._unit)
else:
return _Types.Angle(value, unit)._convert_to(self._unit)
[docs]
class Charge(Requirement):
"""A charge requirement.
Examples
--------
Create a charge requirement with a default of 3 electron charge.
>>> import BioSimSpace as BSS
>>> my_charge = BSS.Gateway.Charge(help="A charge requirement", default=3, unit="electron charge")
The same, but explicitly passing a :class:`Charge <BioSimSpace.Types.Charge>`
for the default.
>>> import BioSimSpace as BSS
>>> my_charge = BSS.Gateway.Charge(help="A charge requirement", default=3*BSS.Units.Charge.electron_charge)
Create a charge requirement with a default of 3 electron charge and a
maximum of -10 Coulomb. Note that the unit is taken from the default value.
>>> import BioSimSpace as BSS
>>> my_charge = BSS.Gateway.Charge(help="A charge requirement",
... default=3*BSS.Units.Charge.electron_charge,
... maximum=10*BSS.Units.Charge.coulomb)
"""
# Set the argparse argument type.
_arg_type = str
[docs]
def __init__(
self,
help=None,
default=None,
unit=None,
minimum=None,
maximum=None,
allowed=None,
):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : :class:`Charge <BioSimSpace.Types.Charge>`
The default value.
unit : str
The unit.
minimum : :class:`Charge <BioSimSpace.Types.Charge>`
The minimum allowed value.
maximum : :class:`Charge <BioSimSpace.Types.Charge>`
The maximum allowed value.
allowed : [:class:`Charge <BioSimSpace.Types.Charge>`]
A list of allowed values.
"""
# Validate the unit.
if unit is not None:
nrg = _Types.Charge("1 %s" % unit)
self._unit = nrg.unit()
self._print_unit = nrg._print_format[nrg.unit()]
else:
try:
self._unit = default.unit()
except:
raise ValueError("No unit or default value has been specified!")
# Call the base class constructor.
super().__init__(
help=help,
default=default,
unit=self._unit,
minimum=minimum,
maximum=maximum,
allowed=allowed,
)
[docs]
def getValue(self):
"""
Return the value.
Returns
-------
value : :class:`Charge <BioSimSpace.Types.Charge>`
The value of the requirement.
"""
if self._value is None:
return None
else:
return _copy.deepcopy(self._value)
def _validate(self, value):
"""Validate that the value is of the correct type."""
if isinstance(value, _Types.Charge):
return value._convert_to(self._unit)
else:
# Extract the value and unit from the argument string.
value, unit = _validate_unit_requirement(value, "energy")
if unit is None:
return _Types.Charge(value, self._unit)
else:
return _Types.Charge(value, unit)._convert_to(self._unit)
[docs]
class Energy(Requirement):
"""An energy requirement.
Examples
--------
Create an energy requirement with a default of 3 kcal per mol.
>>> import BioSimSpace as BSS
>>> my_energy = BSS.Gateway.Energy(help="An energy requirement", default=3, unit="kcal per mol")
The same, but explicitly passing a :class:`Energy <BioSimSpace.Types.Energy>`
for the default.
>>> import BioSimSpace as BSS
>>> my_energy = BSS.Gateway.Energy(help="An energy requirement", default=3*BSS.Units.Energy.kcal_per_mol)
Create an energy requirement with a default of 3 kcal per mol and a
maximum of 50 kJ per mol. Note that the unit is taken from the default value.
>>> import BioSimSpace as BSS
>>> my_energy = BSS.Gateway.Energy(help="An energy requirement",
... default=3*BSS.Units.Energy.kcal_per_mol,
... maximum=50*BSS.Units.Energy.kj_per_mol)
"""
# Set the argparse argument type.
_arg_type = str
[docs]
def __init__(
self,
help=None,
default=None,
unit=None,
minimum=None,
maximum=None,
allowed=None,
):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : :class:`Energy <BioSimSpace.Types.Energy>`
The default value.
unit : str
The unit.
minimum : :class:`Energy <BioSimSpace.Types.Energy>`
The minimum allowed value.
maximum : :class:`Energy <BioSimSpace.Types.Energy>`
The maximum allowed value.
allowed : [:class:`Energy <BioSimSpace.Types.Energy>`]
A list of allowed values.
"""
# Validate the unit.
if unit is not None:
nrg = _Types.Energy("1 %s" % unit)
self._unit = nrg.unit()
self._print_unit = nrg._print_format[nrg.unit()]
else:
try:
self._unit = default.unit()
except:
raise ValueError("No unit or default value has been specified!")
# Call the base class constructor.
super().__init__(
help=help,
default=default,
unit=self._unit,
minimum=minimum,
maximum=maximum,
allowed=allowed,
)
[docs]
def getValue(self):
"""
Return the value.
Returns
-------
value : :class:`Energy <BioSimSpace.Types.Energy>`
"""
if self._value is None:
return None
else:
return _copy.deepcopy(self._value)
def _validate(self, value):
"""Validate that the value is of the correct type."""
if isinstance(value, _Types.Energy):
return value._convert_to(self._unit)
else:
# Extract the value and unit from the argument string.
value, unit = _validate_unit_requirement(value, "energy")
if unit is None:
return _Types.Energy(value, self._unit)
else:
return _Types.Energy(value, unit)._convert_to(self._unit)
[docs]
class Pressure(Requirement):
"""A pressure requirement.
Examples
--------
Create a pressure requirement with a default of 1 atmosphere.
>>> import BioSimSpace as BSS
>>> my_pressure = BSS.Gateway.Pressure(help="A pressure requirement", default=1, unit="atm")
The same, but explicitly passing a :class:`Pressure <BioSimSpace.Types.Pressure>`
for the default.
>>> import BioSimSpace as BSS
>>> my_pressure = BSS.Gateway.Pressure(help="A pressure requirement", default=BSS.Units.Pressure.atm)
Create a pressure requirement with a default of 1 atomosphere and a
maximum of 10 bar. Note that the unit is taken from the default value.
>>> import BioSimSpace as BSS
>>> my_pressure = BSS.Gateway.Pressure(help="A pressure requirement",
... default=BSS.Units.Pressure.atm,
... maximum=10*BSS.Units.Pressure.bar)
"""
# Set the argparse argument type.
_arg_type = str
[docs]
def __init__(
self,
help=None,
default=None,
unit=None,
minimum=None,
maximum=None,
allowed=None,
):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : :class:`Pressure <BioSimSpace.Types.Pressure>`
The default value.
unit : str
The unit.
minimum : :class:`Pressure <BioSimSpace.Types.Pressure>`
The minimum allowed value.
maximum : :class:`Pressure <BioSimSpace.Types.Pressure>`
The maximum allowed value.
allowed : [:class:`Pressure <BioSimSpace.Types.Pressure>`]
A list of allowed values.
"""
# Validate the unit.
if unit is not None:
press = _Types.Pressure("1 %s" % unit)
self._unit = press.unit()
self._print_unit = press._print_format[press.unit()]
else:
try:
self._unit = default.unit()
except:
raise ValueError("No unit or default value has been specified!")
# Call the base class constructor.
super().__init__(
help=help,
default=default,
unit=self._unit,
minimum=minimum,
maximum=maximum,
allowed=allowed,
)
[docs]
def getValue(self):
"""
Return the value.
Returns
-------
value : :class:`Pressure <BioSimSpace.Types.Pressure>`
The value of the requirement.
"""
if self._value is None:
return None
else:
return _copy.deepcopy(self._value)
def _validate(self, value):
"""Validate that the value is of the correct type."""
if isinstance(value, _Types.Pressure):
return value._convert_to(self._unit)
else:
# Extract the value and unit from the argument string.
value, unit = _validate_unit_requirement(value, "pressure")
if unit is None:
return _Types.Pressure(value, self._unit)
else:
return _Types.Pressure(value, unit)._convert_to(self._unit)
[docs]
class Temperature(Requirement):
"""A temperature requirement.
Examples
--------
Create a temperature requirement with a default of 300 kelvin.
>>> import BioSimSpace as BSS
>>> my_temperature = BSS.Gateway.Temperature(help="A temperature requirement", default=300, unit="kelvin")
The same, but explicitly passing a :class:`Temperature <BioSimSpace.Types.Temperature>`
for the default.
>>> import BioSimSpace as BSS
>>> my_temperature = BSS.Gateway.Temperature(help="A temperature requirement", default=300*BSS.Units.Temperature.kelvin)
Create a temperature requirement with a default of 300 Kelvin and a
maximum of 100 Celsius. Note that the unit is taken from the default value.
>>> import BioSimSpace as BSS
>>> my_temperature = BSS.Gateway.Temperature(help="A temperature requirement",
... default=300*BSS.Units.Temperature.kelvin,
... maximum=100*BSS.Units.Temperature.celsius)
"""
# Set the argparse argument type.
_arg_type = str
[docs]
def __init__(
self,
help=None,
default=None,
unit=None,
minimum=None,
maximum=None,
allowed=None,
):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : :class:`Temperature <BioSimSpace.Types.Temperature>`
The default value.
unit : str
The unit.
minimum : :class:`Temperature <BioSimSpace.Types.Temperature>`
The minimum allowed value.
maximum : :class:`Temperature <BioSimSpace.Types.Temperature>`
The maximum allowed value.
allowed : [:class:`Temperature <BioSimSpace.Types.Temperature>`]
A list of allowed values.
"""
# Validate the unit.
if unit is not None:
temp = _Types.Temperature("1 %s" % unit)
self._unit = temp.unit()
self._print_unit = temp._print_format[temp.unit()]
else:
try:
self._unit = default.unit()
except:
raise ValueError("No unit or default value has been specified!")
# Call the base class constructor.
super().__init__(
help=help,
default=default,
unit=self._unit,
minimum=minimum,
maximum=maximum,
allowed=allowed,
)
[docs]
def getValue(self):
"""
Return the value.
Returns
-------
value : :class:`Temperature <BioSimSpace.Types.Temperature>`
The value of the requirement.
"""
if self._value is None:
return None
else:
return _copy.deepcopy(self._value)
def _validate(self, value):
"""Validate that the value is of the correct type."""
if isinstance(value, _Types.Temperature):
return value._convert_to(self._unit)
else:
# Extract the value and unit from the argument string.
value, unit = _validate_unit_requirement(value, "temperature")
if unit is None:
return _Types.Temperature(value, self._unit)
else:
return _Types.Temperature(value, unit)._convert_to(self._unit)
[docs]
class Time(Requirement):
"""A time requirement.
Examples
--------
Create a time requirement with a default of 35 minutes.
>>> import BioSimSpace as BSS
>>> my_time = BSS.Gateway.Time(help="A time requirement", default=35, unit="minutes")
The same, but explicitly passing a :class:`Time <BioSimSpace.Types.Time>`
for the default.
>>> import BioSimSpace as BSS
>>> my_time = BSS.Gateway.Time(help="A time requirement", default=35*BSS.Units.Time.minute)
Create a time requirement with a default of 35 minutes and a maximum
of 5 hours. Note that the unit is taken from the default value.
>>> import BioSimSpace as BSS
>>> my_time = BSS.Gateway.Time(help="A time requirement",
... default=35*BSS.Units.Time.minute,
... maximum=5*BSS.Units.Time.hour)
"""
# Set the argparse argument type.
_arg_type = str
[docs]
def __init__(
self,
help=None,
default=None,
unit=None,
minimum=None,
maximum=None,
allowed=None,
):
"""
Constructor.
Parameters
----------
help : str
The help string.
default : :class:`Time <BioSimSpace.Types.Time>`
The default value.
unit : str
The unit.
minimum : :class:`Time <BioSimSpace.Types.Time>`
The minimum allowed value.
maximum : :class:`Time <BioSimSpace.Types.Time>`
The maximum allowed value.
allowed : [:class:`Time <BioSimSpace.Types.Time>`]
The list of allowed values.
"""
# Validate the unit.
if unit is not None:
time = _Types.Time("1 %s" % unit)
self._unit = time.unit()
self._print_unit = time._print_format[time.unit()]
else:
try:
self._unit = default.unit()
except:
raise ValueError("No unit or default value has been specified!")
# Call the base class constructor.
super().__init__(
help=help,
default=default,
unit=self._unit,
minimum=minimum,
maximum=maximum,
allowed=allowed,
)
[docs]
def getValue(self):
"""
Return the value.
Returns
-------
value : :class:`Time <BioSimSpace.Types.Time>`
"""
if self._value is None:
return None
else:
return _copy.deepcopy(self._value)
def _validate(self, value):
"""Validate that the value is of the correct type."""
if isinstance(value, _Types.Time):
return value._convert_to(self._unit)
else:
# Extract the value and unit from the argument string.
value, unit = _validate_unit_requirement(value, "time")
if unit is None:
return _Types.Time(value, self._unit)
else:
return _Types.Time(value, unit)._convert_to(self._unit)
def _validate_unit_requirement(value, unit_type):
"""
Helper function to validate input requirements with units.
Parameters
----------
value : str
The value of the input requirement.
unit_type : str
The unit type.
Returns
-------
(value, unit) : tuple
The value and unit of the requirement.
"""
# No unit by default.
unit = None
# Float.
if isinstance(value, float):
pass
# Integer.
elif type(value) is int:
value = float(value)
# String.
elif isinstance(value, str):
# First try to directly convert to a float.
try:
value = float(value)
# Use a regular expression to extract the value and unit.
except ValueError:
# Strip white space from the string.
value = value.replace(" ", "")
# Try to match scientific format.
match = _re.search(r"(\-?\d+\.?\d*e\-?\d+)(.*)", value, _re.IGNORECASE)
# Try to match decimal format.
if match is None:
match = _re.search(r"(\-?\d+\.?\d*)(.*)", value, _re.IGNORECASE)
# No matches, raise an error.
if match is None:
raise ValueError(
"Could not interpret %s: '%s'" % (unit_type, value)
)
# Extract the value and unit.
value, unit = match.groups()
# Convert the value to a float.
value = float(value)
# Unsupported.
else:
raise TypeError(
"Unsupported value type '%s'. Options are 'float', 'int', or 'str'."
% type(value)
)
return (value, unit)
def _unarchive(name):
"""
Decompress an archive and return a list of files.
Parameters
----------
name : str
The name of the archive (full path).
Returns
-------
files : [ str ]
A list of file names.
"""
# Get the directory name.
dir = _os.path.dirname(name)
# If the file compressed file has been passed on the command-line, then
# we'll extract it to a directory to avoid littering the current workspace.
if dir == "uploads":
dir += "/"
else:
dir = _os.path.splitext(name)[0] + "/"
# List of supported tar file formats.
tarfiles = ["tar.gz", "tar.bz2", "tar"]
# Check whether this is a tar compressed file.
for tar_name in tarfiles:
# Found a match.
if tar_name in name.lower():
# The list of decompressed files.
files = []
# Decompress the archive.
with _tarfile.open(name) as tar:
# We need to call tar.list(), otherwise the tar object will not know
# about nested directories, i.e. it will appear as if there is a single
# member.
print("Decompressing...")
tar.list(verbose=False)
# Loop over all of the members and get the file names.
# If the name has no extension, then we assume that it's a directory.
for file in tar.members:
if _os.path.splitext(file.name)[1] != "":
files.append(dir + file.name)
# Now extract all of the files.
tar.extractall(dir)
return files
# Get the file name and extension.
file, ext = _os.path.splitext(name)
# This is a zip file.
if ext.lower() == ".zip":
# The list of decompressed files.
files = None
with _zipfile.ZipFile(name) as zip:
files = zip.namelist()
print("Decompressing...")
for file in files:
print(file)
zip.extractall(dir)
return [dir + file for file in files]
# This is a gzip file.
if ext.lower() == ".gz" or ext == ".gzip":
with _gzip.open(name, "rb") as f_in:
with open(file, "wb") as f_out:
print("Decompressing...\n%s" % name)
_shutil.copyfileobj(f_in, f_out)
return file
# This is a bzip2 file.
if ext.lower() == ".bz2" or ext == ".bzip2":
with _bz2.open(name, "rb") as f_in:
with open(file, "wb") as f_out:
print("Decompressing...\n%s" % name)
_shutil.copyfileobj(f_in, f_out)
return file
# If we get this far, then this is not a supported archive.
return None