Require pybind 2.6 from environment for gmxapi Python package extension module.
[alexxy/gromacs.git] / python_packaging / src / gmxapi / version.py
index 2f1582b7cc5277f336eebc0d8b351314a2842e83..7d4b7fa6393af1c0c51a035ffa6e095769e34017 100644 (file)
@@ -1,7 +1,7 @@
 #
 # 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::
 
-"""
-import warnings
+    pkg_resources.get_distribution('gmxapi').version
+
+`gmxapi.version` module functions `api_is_at_least()` and `has_feature()`
+support additional convenience and introspection.
+
+.. versionchanged:: 0.2
+
+    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/>`.
 
-__version__ = "0.1.0"
+.. seealso::
 
-# TODO: (pending infrastructure and further discussion) Configure with CMake.
-# __version__ = "@PROJECT_VERSION@"
-# major = @PROJECT_VERSION_MAJOR@
-# minor = @PROJECT_VERSION_MINOR@
-# patch = @PROJECT_VERSION_PATCH@
+    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.')
+
+.. todo:: Use pkg_resources.get_distribution('gmxapi').version and
+          "development installations" instead of relying on or publicizing
+          a __version__ attribute.
+"""
+import warnings
 
-from gmxapi.exceptions import FeatureNotAvailableError
+from .exceptions import FeatureNotAvailableError
 
-major = 0
-minor = 1
-patch = 0
+# TODO: Version management policy and procedures.
+_major = 0
+_minor = 3
+_micro = 0
+_suffix = 'a5'
 
-# Note: this is not automatically updated. See RELEASE.txt and https://github.com/kassonlab/gmxapi/issues/152
-release = True
+# 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']
@@ -77,7 +97,7 @@ _named_features_0_1 = []
 # 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://redmine.gromacs.org/issues/2893
+# 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)
@@ -119,17 +139,17 @@ 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
@@ -138,7 +158,7 @@ def has_feature(name='', enable_exception=False):
     development branches. Users should refer to the documentation for the package
     modules and API level.
 
-    The primary use case is, in conjunction with api_is_at_least(), to allow
+    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
@@ -147,19 +167,19 @@ def has_feature(name='', enable_exception=False):
     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
+    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
+    `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.exceptions.FeatureNotAvailableError if `enable_exception == True` and feature is not found.
+        gmxapi.exceptions.FeatureNotAvailableError: If ``enable_exception == True`` and feature is not found.
 
     """
     # First, issue a warning if the feature name is subject to removal because
@@ -172,7 +192,8 @@ def has_feature(name='', enable_exception=False):
         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
+                category=DeprecationWarning,
+                stacklevel=2
             )
 
     # Check whether the feature is listed in the API specification amendments.