diff --git a/package/CHANGELOG b/package/CHANGELOG index 03255eb4ad..9c14682673 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -14,7 +14,7 @@ The rules for this file: ------------------------------------------------------------------------------- -??/??/?? IAlibay, ChiahsinChu, RMeli, tanishy7777, talagayev +??/??/?? IAlibay, ChiahsinChu, RMeli, tanishy7777, talagayev, hmacdope * 2.9.0 @@ -25,6 +25,7 @@ Fixes the function to prevent shared state. (Issue #4655) Enhancements + * Handling of Pathlib.Path in SingleFrameReaderBase, Topology and Universe (Issue #3937) * Enables parallelization for analysis.contacts.Contacts (Issue #4660) * Enable parallelization for analysis.nucleicacids.NucPairDist (Issue #4670) * Add check and warning for empty (all zero) coordinates in RDKit converter (PR #4824) diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index 61afa29e7d..2bf0b176e1 100644 --- a/package/MDAnalysis/coordinates/base.py +++ b/package/MDAnalysis/coordinates/base.py @@ -1662,7 +1662,11 @@ class SingleFrameReaderBase(ProtoReader): def __init__(self, filename, convert_units=True, n_atoms=None, **kwargs): super(SingleFrameReaderBase, self).__init__() - self.filename = filename + if isinstance(filename, NamedStream): + self.filename = filename + else: + self.filename = str(filename) + self.convert_units = convert_units self.n_frames = 1 diff --git a/package/MDAnalysis/core/universe.py b/package/MDAnalysis/core/universe.py index 504d07c02c..181c552192 100644 --- a/package/MDAnalysis/core/universe.py +++ b/package/MDAnalysis/core/universe.py @@ -60,6 +60,7 @@ import warnings import contextlib import collections +import pathlib import MDAnalysis import sys @@ -102,6 +103,9 @@ def _check_file_like(topology): else: _name = None return NamedStream(topology, _name) + + elif isinstance(topology, pathlib.Path): + return str(topology) return topology def _topology_from_file_like(topology_file, topology_format=None, diff --git a/package/MDAnalysis/topology/base.py b/package/MDAnalysis/topology/base.py index f4ae0894e4..e20d033cf3 100644 --- a/package/MDAnalysis/topology/base.py +++ b/package/MDAnalysis/topology/base.py @@ -47,6 +47,7 @@ from ..lib import util + class _Topologymeta(type): """Internal: Topology Parser registration voodoo @@ -115,7 +116,12 @@ class TopologyReaderBase(IOBase, metaclass=_Topologymeta): Added keyword 'universe' to pass to Atom creation. """ def __init__(self, filename): - self.filename = filename + + if isinstance(filename, util.NamedStream): + self.filename = filename + else: + self.filename = str(filename) + def parse(self, **kwargs): # pragma: no cover raise NotImplementedError("Override this in each subclass") diff --git a/testsuite/MDAnalysisTests/coordinates/base.py b/testsuite/MDAnalysisTests/coordinates/base.py index dafeeedca3..2df0101472 100644 --- a/testsuite/MDAnalysisTests/coordinates/base.py +++ b/testsuite/MDAnalysisTests/coordinates/base.py @@ -25,12 +25,14 @@ import numpy as np import pytest +from pathlib import Path from unittest import TestCase from numpy.testing import (assert_equal, assert_almost_equal, assert_array_almost_equal, assert_allclose) import MDAnalysis as mda from MDAnalysis.coordinates.timestep import Timestep +from MDAnalysis.coordinates.memory import MemoryReader from MDAnalysis.transformations import translate @@ -134,6 +136,12 @@ def test_pickle_singleframe_reader(self): assert_equal(reader.ts, reader_p.ts, "Modification of ts not preserved after serialization") + def test_pathlib_input_single(self): + path = Path(self.filename) + u_str = mda.Universe(self.filename) + u_path = mda.Universe(path) + assert u_str.atoms.n_atoms == u_path.atoms.n_atoms + class BaseReference(object): def __init__(self): @@ -537,6 +545,16 @@ def test_timeseries_atomgroup_asel_mutex(self, reader): atoms = mda.Universe(reader.filename, to_guess=()).select_atoms("index 1") with pytest.raises(ValueError, match="Cannot provide both"): timeseries = reader.timeseries(atomgroup=atoms, asel=atoms, order='fac') + + def test_pathlib_input_base(self, reader): + if isinstance(reader, MemoryReader): + if isinstance(reader, MemoryReader): + skip_reason = "MemoryReader" + pytest.skip(f"Skipping test for Pathlib input with reason: {skip_reason}") + path = Path(reader.filename) + u_str = mda.Universe(reader.filename) + u_path = mda.Universe(path) + assert u_str.atoms.n_atoms == u_path.atoms.n_atoms class MultiframeReaderTest(BaseReaderTest): diff --git a/testsuite/MDAnalysisTests/coordinates/test_gro.py b/testsuite/MDAnalysisTests/coordinates/test_gro.py index 7dcdbbc029..96ad5121c6 100644 --- a/testsuite/MDAnalysisTests/coordinates/test_gro.py +++ b/testsuite/MDAnalysisTests/coordinates/test_gro.py @@ -46,7 +46,6 @@ assert_equal, ) import pytest -from io import StringIO class TestGROReaderOld(RefAdK): diff --git a/testsuite/MDAnalysisTests/topology/base.py b/testsuite/MDAnalysisTests/topology/base.py index 6527ab8ae3..0a9c4a66d9 100644 --- a/testsuite/MDAnalysisTests/topology/base.py +++ b/testsuite/MDAnalysisTests/topology/base.py @@ -91,6 +91,14 @@ def test_creates_universe(self, filename): u = mda.Universe(filename) assert isinstance(u, mda.Universe) + def test_pathlib_input(self, filename): + """Check that pathlib.Path objects are accepted""" + import pathlib + path = pathlib.Path(filename) + u_str = mda.Universe(filename) + u_path = mda.Universe(path) + assert u_str.atoms.n_atoms == u_path.atoms.n_atoms + def test_guessed_attributes(self, filename): """check that the universe created with certain parser have the same guessed attributes as when it was guessed inside the parser"""