7d4b7fa6393af1c0c51a035ffa6e095769e34017
[alexxy/gromacs.git] / python_packaging / src / gmxapi / version.py
1 #
2 # This file is part of the GROMACS molecular simulation package.
3 #
4 # Copyright (c) 2019,2020,2021, by the GROMACS development team, led by
5 # Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 # and including many others, as listed in the AUTHORS file in the
7 # top-level source directory and at http://www.gromacs.org.
8 #
9 # GROMACS is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU Lesser General Public License
11 # as published by the Free Software Foundation; either version 2.1
12 # of the License, or (at your option) any later version.
13 #
14 # GROMACS is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 # Lesser General Public License for more details.
18 #
19 # You should have received a copy of the GNU Lesser General Public
20 # License along with GROMACS; if not, see
21 # http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23 #
24 # If you want to redistribute modifications to GROMACS, please
25 # consider that scientific software is very special. Version
26 # control is crucial - bugs must be traceable. We will be happy to
27 # consider code for inclusion in the official distribution, but
28 # derived work must not be called official GROMACS. Details are found
29 # in the README & COPYING files - if they are missing, get the
30 # official version at http://www.gromacs.org.
31 #
32 # To help us fund GROMACS development, we humbly ask that you cite
33 # the research papers on the package. Check out http://www.gromacs.org.
34
35 """
36 gmxapi version and release information.
37
38 The ``gmxapi.__version__`` attribute contains a :pep:`version string <440>`.
39 The more general way to access the package version is with the
40 :py:mod:`pkg_resources <https://setuptools.readthedocs.io/en/latest/pkg_resources.html>` module::
41
42     pkg_resources.get_distribution('gmxapi').version
43
44 `gmxapi.version` module functions `api_is_at_least()` and `has_feature()`
45 support additional convenience and introspection.
46
47 .. versionchanged:: 0.2
48
49     This module no longer provides public data attributes.
50     Instead, use the module functions or
51     :py:mod:`packaging.version <https://packaging.pypa.io/en/latest/version/>`.
52
53 .. seealso::
54
55     Consider https://packaging.pypa.io/en/latest/version/ for programmatic
56     handling of the version string. For example::
57
58         from packaging.version import parse
59         gmxapi_version = pkg_resources.get_distribution('gmxapi').version
60         if parse(gmxapi_version).is_prerelease:
61             print('The early bird gets the worm.')
62
63 .. todo:: Use pkg_resources.get_distribution('gmxapi').version and
64           "development installations" instead of relying on or publicizing
65           a __version__ attribute.
66 """
67 import warnings
68
69 from .exceptions import FeatureNotAvailableError
70
71 # TODO: Version management policy and procedures.
72 _major = 0
73 _minor = 3
74 _micro = 0
75 _suffix = 'a5'
76
77 # Reference https://www.python.org/dev/peps/pep-0440/
78 # and https://packaging.pypa.io/en/latest/version/
79 __version__ = '{major}.{minor}.{micro}{suffix}'.format(major=_major,
80                                                        minor=_minor,
81                                                        micro=_micro,
82                                                        suffix=_suffix)
83
84 # Features added since the initial gmxapi prototype, targeted for version 0.1.
85 _named_features_0_0 = ['fr1', 'fr3', 'fr7', 'fr15']
86 # Features named since the finalization of the 0.1 specification with GROMACS 2020.
87 _named_features_0_1 = []
88 # Named features describe functionality or behavior introduced since the last
89 # major release, and should be described in gmxapi documentation or issue
90 # tracking system. Note that, as features become part of the specification,
91 # conditionals depending on them should be phased out of the package source. At
92 # major releases, the named feature list should be reset to empty. Optionally,
93 # we could raise a DeprecationWarning for calls to has_feature() for features
94 # that have become part of the specification, at least for a few minor release or
95 # a few years, to avoid introducing errors to client code.
96 #
97 # Bugs and bug fixes may be indicated with names consisting of tracked issue URLs.
98 #
99 # Features consisting of 'fr' and a numeric suffix are the functional requirements
100 # described in roadmap.rst, as described at https://gitlab.com/gromacs/gromacs/-/issues/2893
101 #
102 # fr1: wrap importable Python code.
103 # fr2: output proxy establishes execution dependency (superseded by fr3)
104 # fr3: output proxy can be used as input
105 # fr4: dimensionality and typing of named data causes generation of correct work topologies
106 # fr5: explicit many-to-one or many-to-many data flow
107 # fr7: Python bindings for launching simulations
108 # fr8: gmx.mdrun understands ensemble work
109 # fr9: MD plugins
110 # fr10: fused operations for use in looping constructs
111 # fr11: Python access to TPR file contents
112 # fr12: Simulation checkpoint handling
113 # fr13: ``run`` module function simplifies user experience
114 # fr14: Easy access to GROMACS run time parameters
115 # fr15: Simulation input modification
116 # fr16: Create simulation input from simulation output
117 # fr17: Prepare simulation input from multiple sources
118 # fr18: GROMACS CLI tools receive improved Python-level support over generic commandline_operations
119 # fr19: GROMACS CLI tools receive improved C++-level support over generic commandline_operations
120 # fr20: Python bindings use C++ API for expressing user interface
121 # fr21 User insulated from filesystem paths
122 # fr22 MPI-based ensemble management from Python
123 # fr23 Ensemble simulations can themselves use MPI
124
125
126 def api_is_at_least(major_version, minor_version=0, patch_version=0):
127     """Allow client to check whether installed module supports the requested API level.
128
129     Arguments:
130         major_version (int): gmxapi major version number.
131         minor_version (int): optional gmxapi minor version number (default: 0).
132         patch_version (int): optional gmxapi patch level number (default: 0).
133
134     Returns:
135         True if installed gmx package is greater than or equal to the input level
136
137     Note that if gmxapi.version.release is False, the package is not guaranteed to correctly or
138     fully support the reported API level.
139     """
140     if not isinstance(major_version, int) or not isinstance(minor_version, int) or not isinstance(patch_version, int):
141         raise TypeError('Version levels must be provided as integers.')
142     if _major > major_version:
143         return True
144     elif _major == major_version and _minor >= minor_version:
145         return True
146     elif _major == major_version and _minor == minor_version and _micro >= patch_version:
147         return True
148     else:
149         return False
150
151
152 def has_feature(name='', enable_exception=False) -> bool:
153     """Query whether a named feature is available in the installed package.
154
155     Between updates to the API specification, new features or experimental aspects
156     may be introduced into the package and need to be detectable. This function
157     is intended to facilitate code testing and resolving differences between
158     development branches. Users should refer to the documentation for the package
159     modules and API level.
160
161     The primary use case is, in conjunction with `api_is_at_least()`, to allow
162     client code to robustly identify expected behavior and API support through
163     conditional execution and branching. Note that behavior is strongly
164     specified by the API major version number. Features that have become part of
165     the specification and bug-fixes referring to previous major versions should
166     not be checked with *has_feature()*. Using *has_feature()* with old feature
167     names will produce a DeprecationWarning for at least one major version, and
168     client code should be updated to avoid logic errors in future versions.
169
170     For convenience, setting ``enable_exception = True`` causes the function to
171     instead raise a gmxapi.exceptions.FeatureNotAvailableError for unrecognized feature names.
172     This allows extension code to cleanly produce a gmxapi exception instead of
173     first performing a boolean check. Also, some code may be unexecutable for
174     more than one reason, and sometimes it is cleaner to catch all
175     `gmxapi.exceptions.Error` exceptions for a code block, rather than to
176     construct complex conditionals.
177
178     Returns:
179         True if named feature is recognized by the installed package, else False.
180
181     Raises:
182         gmxapi.exceptions.FeatureNotAvailableError: If ``enable_exception == True`` and feature is not found.
183
184     """
185     # First, issue a warning if the feature name is subject to removal because
186     # of the history of the API specification.
187     if api_is_at_least(0, 2):
188         # For sufficiently advanced API versions, we want to warn that old
189         # feature checks lose meaning and should no longer be checked.
190         # We provide a suggestion with the API version that absorbed their
191         # specification.
192         if name in _named_features_0_0:
193             warnings.warn(
194                 'Old feature name. Use `api_is_at_least(0, 1)` instead of `has_feature({})`.'.format(name),
195                 category=DeprecationWarning,
196                 stacklevel=2
197             )
198
199     # Check whether the feature is listed in the API specification amendments.
200     if name in _named_features_0_0 + _named_features_0_1:
201         return True
202     else:
203         if enable_exception:
204             raise FeatureNotAvailableError('Feature {} not available.'.format(str(name)))
205         return False