diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bd9d1772..8190a5a1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -25,27 +25,13 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macOS-latest] - python-version: ["2.7", "3.6", "3.7", "3.8", "3.9", "3.10", "3.11"] - gromacs-version: ["2021.1"] + os: [ubuntu-latest] + python-version: ["3.8", "3.9", "3.10", "3.11"] + gromacs-version: ["2023.1"] # Test other GROMACS versions selectively on a recent Python. # On macOS only test one GROMACS version and two Python versions # to keep the testing matrix manageable. - exclude: - - os: macOS-latest - python-version: "3.6" - - os: macOS-latest - python-version: "3.7" - - os: macOS-latest - python-version: "3.8" - - os: macOS-latest - python-version: "3.9" - - os: macOS-latest - python-version: "3.10" include: - - os: ubuntu-latest - python-version: "2.7" - gromacs-version: "4.6.5" - os: ubuntu-latest python-version: "3.11" gromacs-version: "4.6.5" @@ -60,10 +46,18 @@ jobs: gromacs-version: "2020.6" - os: ubuntu-latest python-version: "3.11" - gromacs-version: "2022.4" + gromacs-version: "2021.1" - os: ubuntu-latest python-version: "3.11" - gromacs-version: "2023.1" + gromacs-version: "2022.4" + # explicitly include a few macOS runners + # (conda-forge GROMACS >= 2021 is compiled to use rdtscp CPU instruction but these + # are not available in the macOS GitHub runner so we need to use a different GROMACS + # version. Try 2018.5 from bioconda. (2021.5 worked previously but not anymore.) + # Locally 2023.1 was successfully tested on Intel macOS 13.5.) + - os: macOS-latest + python-version: "3.11" + gromacs-version: "2018.6" env: @@ -95,6 +89,7 @@ jobs: run: | micromamba info micromamba list + cat /proc/cpuinfo || (/usr/sbin/system_profiler SPHardwareDataType; /usr/sbin/sysctl -a | grep machdep.cpu) - name: Install pytest and plugins run: | @@ -103,7 +98,6 @@ jobs: - name: Install GROMACS (${{ matrix.gromacs-version }}) # UGLY HACK/FIX (probably to issues with bioconda packages) # - For 3.9, micromamba insists on using pypy 3.9 (and --py-pin does not help). - # - For 2.7 macOS, numkit would get uninstalled. # We can't freeze because then it's not possible to install older GROMACS versions from bioconda. run: | micromamba install 'gromacs==${{ matrix.gromacs-version }}' pocl numkit python=${{ matrix.python-version }}.*=*_cpython diff --git a/CHANGES b/CHANGES index b8323e83..2b685dcd 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,12 @@ CHANGELOG for GromacsWrapper ============================== +2023-xx-xx 0.9.0 +orbeckst + +* removed support for legacy Python (<= 3.7) (#259) + + 2023-09-16 0.8.5 orbeckst diff --git a/README.rst b/README.rst index 92200f0e..65181b0d 100644 --- a/README.rst +++ b/README.rst @@ -11,8 +11,8 @@ A primitive Python wrapper around the Gromacs_ tools. The library is tested with GROMACS 4.6.5, 2018.x, 2019.x, 2020.x, 2021.x, 2022.x, -2023.x (and 5.x and 2016.x should also work). It supports Python 2.7 -and 3.6--3.11 on Linux and macOS. +2023.x (and 5.x and 2016.x should also work). It supports Python +3.8--3.11 on Linux and macOS. GromacsWrapper also provides a small library (cook book) of often-used recipes and helper functions to set up MD simulations. diff --git a/doc/sphinx/source/installation.txt b/doc/sphinx/source/installation.txt index 83aed698..85b826e8 100644 --- a/doc/sphinx/source/installation.txt +++ b/doc/sphinx/source/installation.txt @@ -6,7 +6,6 @@ Installation ============== - This document should help you to install the **GromacsWrapper** package. Please raise and issue in the `Issue Tracker`_ if problems occur or if you have suggestions on how to improve the package or @@ -103,19 +102,17 @@ formatting so please install black_ and run it on your code. Requirements ============ -Python_ 2.7.x or Python >= 3.6 and GROMACS_ (4.6.x, 2016, 2018, 2019, -2020, 2021, 2022) must be installed. ipython_ is very much -recommended. +Python_ >= 3.8 and GROMACS_ (4.6.x, 2016, 2018, 2019, 2020, 2021, +2022, 2023) must be installed. .. _Python: http://www.python.org -.. _ipython: http://ipython.scipy.org System requirements ------------------- -Tested with Python 2.7.x and Python 3.6--3.11 on Linux and Mac -OS X. Earlier Python versions are not supported. +Tested with Python 3.8--3.11 on Linux and Mac OS X. Earlier Python +versions were only supported until release 0.8.5. Required Python modules diff --git a/gromacs/__init__.py b/gromacs/__init__.py index f73e0d09..65353f4a 100644 --- a/gromacs/__init__.py +++ b/gromacs/__init__.py @@ -169,8 +169,6 @@ .. autodata:: __version__ """ -from __future__ import absolute_import - __docformat__ = "restructuredtext en" import os diff --git a/gromacs/cbook.py b/gromacs/cbook.py index 9be86fa1..4b56a3e6 100644 --- a/gromacs/cbook.py +++ b/gromacs/cbook.py @@ -125,8 +125,6 @@ # new class and set arguments explicitly in init (using kwargs['flag'] = # default) ... or I can write some meta(??) class to do this nicely -from __future__ import absolute_import, with_statement - __docformat__ = "restructuredtext en" import sys @@ -136,7 +134,6 @@ import tempfile import shutil import glob -import six import logging diff --git a/gromacs/collections.py b/gromacs/collections.py index 267b5b76..005a53dc 100644 --- a/gromacs/collections.py +++ b/gromacs/collections.py @@ -15,7 +15,7 @@ """ import os.path -from six.moves import cPickle +import pickle from numpy import all, any @@ -50,10 +50,10 @@ def save(self, filename): If no extension is provided, ".collection" is appended. """ - cPickle.dump( + pickle.dump( self, open(self._canonicalize(filename), "wb"), - protocol=cPickle.HIGHEST_PROTOCOL, + protocol=pickle.HIGHEST_PROTOCOL, ) def load(self, filename, append=False): @@ -64,7 +64,7 @@ def load(self, filename, append=False): If no extension is provided, ".collection" is appended. """ - tmp = cPickle.load(open(self._canonicalize(filename), "rb")) + tmp = pickle.load(open(self._canonicalize(filename), "rb")) if append: self.extend(tmp) else: diff --git a/gromacs/config.py b/gromacs/config.py index 559c5011..1ebc5dce 100644 --- a/gromacs/config.py +++ b/gromacs/config.py @@ -225,43 +225,13 @@ .. autodata:: qscript_template """ -from __future__ import absolute_import, with_statement, print_function - import os import logging import re import subprocess import sys -if sys.version_info[0] < 3: # several differences for Python 2 - from ConfigParser import SafeConfigParser as ConfigParser - from ConfigParser import NoSectionError, NoOptionError - - # Define read_file to point to the (deprecated in Python 3) readfp - # in order to have consistent, non-deprecated syntax - ConfigParser.read_file = ConfigParser.readfp - - # Implement the new `fallback` kwarg based on the Python 3.7 implementation - # https://github.com/python/cpython/blob/3.7/Lib/configparser.py#L804 - # This should ensure backwards compatibility. - _cf_getbool = ConfigParser.getboolean - _UNSET = object() - - def _getboolean(self, section, option, fallback=_UNSET, **kwargs): - """Return a boolean for the specified config option - - If *fallback* is used, it will be returned if there if the - option is not specified anywhere (defaults, config file).""" - try: # Try using the Python 2 function - return _cf_getbool(self, section, option, **kwargs) - except (NoSectionError, NoOptionError): - if fallback is _UNSET: - raise - return fallback # If fallback is given, use that value - - ConfigParser.getboolean = _getboolean -else: - from configparser import ConfigParser +from configparser import ConfigParser from pkg_resources import resource_filename, resource_listdir diff --git a/gromacs/core.py b/gromacs/core.py index 57674255..3af1e055 100644 --- a/gromacs/core.py +++ b/gromacs/core.py @@ -98,9 +98,6 @@ .. autoclass:: PopenWithInput :members: """ -from __future__ import absolute_import, with_statement, print_function -import six - __docformat__ = "restructuredtext en" import sys @@ -277,13 +274,13 @@ def Popen(self, *args, **kwargs): use_shell = kwargs.pop("use_shell", False) if input: stdin = PIPE - if isinstance(input, six.string_types) and not input.endswith("\n"): + if isinstance(input, str) and not input.endswith("\n"): # make sure that input is a simple string with \n line endings - input = six.text_type(input) + "\n" + input = str(input) + "\n" else: try: # make sure that input is a simple string with \n line endings - input = "\n".join(map(six.text_type, input)) + "\n" + input = "\n".join(map(str, input)) + "\n" except TypeError: # so maybe we are a file or something ... and hope for the best pass @@ -733,9 +730,6 @@ def __init__(self, *args, **kwargs): """ kwargs.setdefault("close_fds", True) # fixes 'Too many open fds' with 2.6 self.input = kwargs.pop("input", None) - if six.PY2 and self.input is not None: - # in Python 2, subprocess.Popen uses os.write(chunk) with default ASCII encoding - self.input = self.input.encode("utf-8") self.command = args[0] try: input_string = ( diff --git a/gromacs/environment.py b/gromacs/environment.py index 08f23069..fa5995f4 100644 --- a/gromacs/environment.py +++ b/gromacs/environment.py @@ -36,7 +36,6 @@ :members: """ -import six # set up flags for core routines (more convoluted than strictly necessary but should @@ -87,7 +86,7 @@ def __setitem__(self, name, value): self.get_flag(name).set(value) def _itervalues(self): - return six.itervalues(super(Flags, self)) + return super(Flags, self).values() def _items(self): return super(Flags, self).items() diff --git a/gromacs/fileformats/__init__.py b/gromacs/fileformats/__init__.py index e4806b2a..7a79604a 100644 --- a/gromacs/fileformats/__init__.py +++ b/gromacs/fileformats/__init__.py @@ -4,7 +4,6 @@ # See the file COPYING for details. # file formats -from __future__ import absolute_import __all__ = ["XVG", "MDP", "NDX", "uniqueNDX", "XPM", "TOP"] diff --git a/gromacs/fileformats/convert.py b/gromacs/fileformats/convert.py index 3b1dc543..87322db3 100644 --- a/gromacs/fileformats/convert.py +++ b/gromacs/fileformats/convert.py @@ -38,7 +38,6 @@ .. autofunction:: to_unicode """ -import six import re @@ -47,17 +46,17 @@ def to_unicode(obj): """Convert obj to unicode (if it can be be converted). Conversion is only attempted if `obj` is a string type (as - determined by :data:`six.string_types`). + determined by :class:`str`). .. versionchanged:: 0.7.0 removed *encoding* keyword argument """ - if not isinstance(obj, six.string_types): + if not isinstance(obj, str): return obj try: - obj = six.text_type(obj) + obj = str(obj) except TypeError: pass return obj diff --git a/gromacs/fileformats/mdp.py b/gromacs/fileformats/mdp.py index d1a07277..e14a2b94 100644 --- a/gromacs/fileformats/mdp.py +++ b/gromacs/fileformats/mdp.py @@ -18,12 +18,9 @@ """ -from __future__ import absolute_import, with_statement - import os, errno import re import warnings -import six import numpy @@ -163,7 +160,7 @@ def write(self, filename=None, skipempty=False): else: # parameter = value if skipempty and (v == "" or v is None): continue - if isinstance(v, six.string_types) or not hasattr(v, "__iter__"): + if isinstance(v, str) or not hasattr(v, "__iter__"): mdp.write("{k!s} = {v!s}\n".format(**vars())) else: mdp.write("{} = {}\n".format(k, " ".join(map(str, v)))) diff --git a/gromacs/fileformats/ndx.py b/gromacs/fileformats/ndx.py index 69150e27..4356a4f2 100644 --- a/gromacs/fileformats/ndx.py +++ b/gromacs/fileformats/ndx.py @@ -23,10 +23,6 @@ .. autoclass:: IndexSet """ -from __future__ import absolute_import, with_statement - -from six.moves import range - import os, errno import re import warnings diff --git a/gromacs/fileformats/top.py b/gromacs/fileformats/top.py index 7fd0f879..fce89983 100644 --- a/gromacs/fileformats/top.py +++ b/gromacs/fileformats/top.py @@ -51,7 +51,6 @@ Exchange (HREX) simulations. See ``scripts/gw-partial_tempering.py`` for an example. """ -from __future__ import absolute_import import textwrap import logging diff --git a/gromacs/fileformats/xpm.py b/gromacs/fileformats/xpm.py index fd203b84..3386cbb8 100644 --- a/gromacs/fileformats/xpm.py +++ b/gromacs/fileformats/xpm.py @@ -69,10 +69,6 @@ """ -from __future__ import absolute_import, with_statement - -from six.moves import range - import os, errno import re import warnings diff --git a/gromacs/fileformats/xvg.py b/gromacs/fileformats/xvg.py index 9b4cfefa..3e54a9a5 100644 --- a/gromacs/fileformats/xvg.py +++ b/gromacs/fileformats/xvg.py @@ -187,9 +187,6 @@ .. autofunction:: break_array """ -from __future__ import with_statement, absolute_import - -from six.moves import zip, range import os import errno diff --git a/gromacs/formats.py b/gromacs/formats.py index d534bb3c..8b952a47 100644 --- a/gromacs/formats.py +++ b/gromacs/formats.py @@ -45,8 +45,6 @@ :members: """ -from __future__ import absolute_import - __docformat__ = "restructuredtext en" __all__ = ["XVG", "MDP", "NDX", "uniqueNDX", "XPM", "TOP"] diff --git a/gromacs/log.py b/gromacs/log.py index 0337aa9f..1160f0f4 100644 --- a/gromacs/log.py +++ b/gromacs/log.py @@ -60,7 +60,6 @@ .. autogenerated, see Online Docs """ -from __future__ import absolute_import import logging diff --git a/gromacs/qsub.py b/gromacs/qsub.py index 8f722b98..114350ee 100644 --- a/gromacs/qsub.py +++ b/gromacs/qsub.py @@ -192,8 +192,6 @@ .. autodata:: queuing_systems """ -from __future__ import absolute_import, with_statement - import os import errno from os.path import relpath diff --git a/gromacs/run.py b/gromacs/run.py index 701141c1..af7094c2 100644 --- a/gromacs/run.py +++ b/gromacs/run.py @@ -96,8 +96,6 @@ class MDrunnerMPI(gromacs.run.MDrunner): .. autofunction:: find_gromacs_command """ -from __future__ import absolute_import, with_statement - __docformat__ = "restructuredtext en" import warnings diff --git a/gromacs/scaling.py b/gromacs/scaling.py index a4802221..e61c3394 100644 --- a/gromacs/scaling.py +++ b/gromacs/scaling.py @@ -20,8 +20,6 @@ .. autofunction:: partial_tempering """ -from __future__ import absolute_import, division, print_function - import math import copy import logging diff --git a/gromacs/setup.py b/gromacs/setup.py index 2beecbc1..55e7e691 100644 --- a/gromacs/setup.py +++ b/gromacs/setup.py @@ -114,9 +114,6 @@ .. autodata:: vdw_lipid_atom_radii """ - -from __future__ import absolute_import, with_statement - __docformat__ = "restructuredtext en" import os diff --git a/gromacs/tools.py b/gromacs/tools.py index 11c4b188..ae025b45 100644 --- a/gromacs/tools.py +++ b/gromacs/tools.py @@ -188,10 +188,6 @@ .. The following is autogenerated with sphinx. """ -from __future__ import absolute_import - -import six - import os.path import tempfile import subprocess @@ -354,11 +350,7 @@ def run(self, *args, **kwargs): def _fake_multi_ndx(self, **kwargs): ndx = kwargs.get("n") - if ( - not (ndx is None or isinstance(ndx, six.string_types)) - and len(ndx) > 1 - and "s" in kwargs - ): + if not (ndx is None or isinstance(ndx, str)) and len(ndx) > 1 and "s" in kwargs: ndx.append(kwargs.get("s")) kwargs["n"] = merge_ndx(*ndx) return kwargs @@ -569,7 +561,7 @@ def merge_ndx(*args): # with only 5.x # update with temporary directory for fancy, cmd in list(registry.items()): - for c5, c4 in six.iteritems(NAMES5TO4): + for c5, c4 in NAMES5TO4.items(): # have to check each one, since it's possible there are suffixes # like for double precision; cmd.command_name is Gromacs name # (e.g. 'convert-tpr') so we need to be careful in the processing below. @@ -679,7 +671,7 @@ def __str__(self): # Append class doc for each command -for name in six.iterkeys(registry): +for name in registry.keys(): __doc__ += ".. class:: {0!s}\n :noindex:\n".format(name) registry["Release"] = Release diff --git a/gromacs/utilities.py b/gromacs/utilities.py index 097760a9..4f1929de 100644 --- a/gromacs/utilities.py +++ b/gromacs/utilities.py @@ -86,14 +86,8 @@ class is derived from it. .. autodata:: amino_acid_codes """ -from __future__ import absolute_import, with_statement, division - __docformat__ = "restructuredtext en" -import six -from six import string_types - -import sys import os import glob import fnmatch @@ -342,7 +336,7 @@ def _get_stream(filename, openfunction=open, mode="r"): # case we have to ignore the error and return None. Second is when openfunction can't open the file because # either the file isn't there or the permissions don't allow access. if errno.errorcode[err.errno] in ["ENOENT", "EACCES"]: - six.reraise(*sys.exc_info()) + raise return None if mode.startswith("r"): # additional check for reading (eg can we uncompress) --- is this needed? @@ -724,7 +718,7 @@ def __repr__(self): def iterable(obj): """Returns ``True`` if *obj* can be iterated over and is *not* a string.""" - if isinstance(obj, string_types): + if isinstance(obj, str): return False # avoid iterating over characters of a string if hasattr(obj, "next"): return True # any iterator will do diff --git a/setup.py b/setup.py index e3b90cf6..713de0f1 100644 --- a/setup.py +++ b/setup.py @@ -4,7 +4,6 @@ # # See the files INSTALL and README for details or visit # https://github.com/Becksteinlab/GromacsWrapper -from __future__ import with_statement from setuptools import setup, find_packages import versioneer @@ -17,7 +16,7 @@ name="GromacsWrapper", version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), - description="A Python wrapper around the Gromacs tools.", + description="A Python wrapper around the GROMACS tools.", long_description=long_description, long_description_content_type="text/x-rst", author="Oliver Beckstein", @@ -35,12 +34,7 @@ "Operating System :: POSIX", "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows ", - "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -70,7 +64,6 @@ }, install_requires=[ "numpy>=1.0", - "six", # towards py 3 compatibility "numkit", # numerical helpers "matplotlib", ],