#
# This file is part of the GROMACS molecular simulation package.
#
-# Copyright (c) 2019, by the GROMACS development team, led by
+# Copyright (c) 2019,2020,2021, by the GROMACS development team, led by
# Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
# and including many others, as listed in the AUTHORS file in the
# top-level source directory and at http://www.gromacs.org.
# the research papers on the package. Check out http://www.gromacs.org.
"""
-Provide version and release information.
+gmxapi version and release information.
-Attributes:
- major (int): gmxapi major version number.
- minor (int): gmxapi minor version number.
- patch (int): gmxapi patch level number.
- release (bool): True if imported gmx module is an officially tagged release, else False.
+The ``gmxapi.__version__`` attribute contains a :pep:`version string <440>`.
+The more general way to access the package version is with the
+:py:mod:`pkg_resources <https://setuptools.readthedocs.io/en/latest/pkg_resources.html>` module::
-"""
-
-__version__ = "0.1.0-dev3"
+ pkg_resources.get_distribution('gmxapi').version
-# TODO: (pending infrastructure and further discussion) Configure with CMake.
-# __version__ = "@PROJECT_VERSION@"
-# major = @PROJECT_VERSION_MAJOR@
-# minor = @PROJECT_VERSION_MINOR@
-# patch = @PROJECT_VERSION_PATCH@
+`gmxapi.version` module functions `api_is_at_least()` and `has_feature()`
+support additional convenience and introspection.
-from gmxapi.exceptions import Error as GmxapiError
+.. versionchanged:: 0.2
-major = 0
-minor = 1
-patch = 0
+ This module no longer provides public data attributes.
+ Instead, use the module functions or
+ :py:mod:`packaging.version <https://packaging.pypa.io/en/latest/version/>`.
-# Note: this is not automatically updated. See RELEASE.txt and https://github.com/kassonlab/gmxapi/issues/152
-release = False
+.. seealso::
-_named_features = ['fr1', 'fr3']
+ Consider https://packaging.pypa.io/en/latest/version/ for programmatic
+ handling of the version string. For example::
+ from packaging.version import parse
+ gmxapi_version = pkg_resources.get_distribution('gmxapi').version
+ if parse(gmxapi_version).is_prerelease:
+ print('The early bird gets the worm.')
-class FeatureError(GmxapiError):
- """Module exception to indicate missing features."""
+.. todo:: Use pkg_resources.get_distribution('gmxapi').version and
+ "development installations" instead of relying on or publicizing
+ a __version__ attribute.
+"""
+import warnings
+
+from .exceptions import FeatureNotAvailableError
+
+# TODO: Version management policy and procedures.
+_major = 0
+_minor = 3
+_micro = 0
+_suffix = 'a5'
+
+# Reference https://www.python.org/dev/peps/pep-0440/
+# and https://packaging.pypa.io/en/latest/version/
+__version__ = '{major}.{minor}.{micro}{suffix}'.format(major=_major,
+ minor=_minor,
+ micro=_micro,
+ suffix=_suffix)
+
+# Features added since the initial gmxapi prototype, targeted for version 0.1.
+_named_features_0_0 = ['fr1', 'fr3', 'fr7', 'fr15']
+# Features named since the finalization of the 0.1 specification with GROMACS 2020.
+_named_features_0_1 = []
+# Named features describe functionality or behavior introduced since the last
+# major release, and should be described in gmxapi documentation or issue
+# tracking system. Note that, as features become part of the specification,
+# conditionals depending on them should be phased out of the package source. At
+# major releases, the named feature list should be reset to empty. Optionally,
+# we could raise a DeprecationWarning for calls to has_feature() for features
+# that have become part of the specification, at least for a few minor release or
+# a few years, to avoid introducing errors to client code.
+#
+# Bugs and bug fixes may be indicated with names consisting of tracked issue URLs.
+#
+# Features consisting of 'fr' and a numeric suffix are the functional requirements
+# described in roadmap.rst, as described at https://gitlab.com/gromacs/gromacs/-/issues/2893
+#
+# fr1: wrap importable Python code.
+# fr2: output proxy establishes execution dependency (superseded by fr3)
+# fr3: output proxy can be used as input
+# fr4: dimensionality and typing of named data causes generation of correct work topologies
+# fr5: explicit many-to-one or many-to-many data flow
+# fr7: Python bindings for launching simulations
+# fr8: gmx.mdrun understands ensemble work
+# fr9: MD plugins
+# fr10: fused operations for use in looping constructs
+# fr11: Python access to TPR file contents
+# fr12: Simulation checkpoint handling
+# fr13: ``run`` module function simplifies user experience
+# fr14: Easy access to GROMACS run time parameters
+# fr15: Simulation input modification
+# fr16: Create simulation input from simulation output
+# fr17: Prepare simulation input from multiple sources
+# fr18: GROMACS CLI tools receive improved Python-level support over generic commandline_operations
+# fr19: GROMACS CLI tools receive improved C++-level support over generic commandline_operations
+# fr20: Python bindings use C++ API for expressing user interface
+# fr21 User insulated from filesystem paths
+# fr22 MPI-based ensemble management from Python
+# fr23 Ensemble simulations can themselves use MPI
def api_is_at_least(major_version, minor_version=0, patch_version=0):
"""
if not isinstance(major_version, int) or not isinstance(minor_version, int) or not isinstance(patch_version, int):
raise TypeError('Version levels must be provided as integers.')
- if major > major_version:
+ if _major > major_version:
return True
- elif major == major_version and minor >= minor_version:
+ elif _major == major_version and _minor >= minor_version:
return True
- elif major == major_version and minor == minor_version and patch >= patch_version:
+ elif _major == major_version and _minor == minor_version and _micro >= patch_version:
return True
else:
return False
-def has_feature(name='', enable_exception=False):
+def has_feature(name='', enable_exception=False) -> bool:
"""Query whether a named feature is available in the installed package.
Between updates to the API specification, new features or experimental aspects
development branches. Users should refer to the documentation for the package
modules and API level.
- The function can be used to get a boolean result or can be used to raise an
- exception in code block by setting `enable_exception=True`
+ The primary use case is, in conjunction with `api_is_at_least()`, to allow
+ client code to robustly identify expected behavior and API support through
+ conditional execution and branching. Note that behavior is strongly
+ specified by the API major version number. Features that have become part of
+ the specification and bug-fixes referring to previous major versions should
+ not be checked with *has_feature()*. Using *has_feature()* with old feature
+ names will produce a DeprecationWarning for at least one major version, and
+ client code should be updated to avoid logic errors in future versions.
+
+ For convenience, setting ``enable_exception = True`` causes the function to
+ instead raise a gmxapi.exceptions.FeatureNotAvailableError for unrecognized feature names.
+ This allows extension code to cleanly produce a gmxapi exception instead of
+ first performing a boolean check. Also, some code may be unexecutable for
+ more than one reason, and sometimes it is cleaner to catch all
+ `gmxapi.exceptions.Error` exceptions for a code block, rather than to
+ construct complex conditionals.
Returns:
True if named feature is recognized by the installed package, else False.
Raises:
- gmxapi.version.FeatureError if `enable_exception == True` and feature is not found.
+ gmxapi.exceptions.FeatureNotAvailableError: If ``enable_exception == True`` and feature is not found.
"""
- if name in _named_features:
+ # First, issue a warning if the feature name is subject to removal because
+ # of the history of the API specification.
+ if api_is_at_least(0, 2):
+ # For sufficiently advanced API versions, we want to warn that old
+ # feature checks lose meaning and should no longer be checked.
+ # We provide a suggestion with the API version that absorbed their
+ # specification.
+ if name in _named_features_0_0:
+ warnings.warn(
+ 'Old feature name. Use `api_is_at_least(0, 1)` instead of `has_feature({})`.'.format(name),
+ category=DeprecationWarning,
+ stacklevel=2
+ )
+
+ # Check whether the feature is listed in the API specification amendments.
+ if name in _named_features_0_0 + _named_features_0_1:
return True
else:
if enable_exception:
- raise FeatureError('Feature {} not available.'.format(str(name)))
+ raise FeatureNotAvailableError('Feature {} not available.'.format(str(name)))
return False