diff --git a/package/CHANGELOG b/package/CHANGELOG index 8ffcae6d24..a19bfed498 100644 --- a/package/CHANGELOG +++ b/package/CHANGELOG @@ -17,7 +17,7 @@ The rules for this file: ??/??/?? IAlibay, HeetVekariya, marinegor, lilyminium, RMeli, ljwoods2, aditya292002, pstaerk, PicoCentauri, BFedder, tyler.je.reddy, SampurnaM, leonwehrhan, kainszs, orionarcher, - yuxuanzhuang, PythonFZ, laksh-krishna-sharma + yuxuanzhuang, PythonFZ, laksh-krishna-sharma, talagayev, hmacdope * 2.8.0 @@ -53,6 +53,7 @@ Fixes * Fix groups.py doctests using sphinx directives (Issue #3925, PR #4374) Enhancements + * Handling of Pathlib.Path in SingleFrameReaderBase, Topology and Universe (Issue #3937) * Introduce parallelization API to `AnalysisBase` and to `analysis.rms.RMSD` class (Issue #4158, PR #4304) * Improve error message for `AtomGroup.unwrap()` when bonds are not present.(Issue #4436, PR #4642) diff --git a/package/MDAnalysis/coordinates/base.py b/package/MDAnalysis/coordinates/base.py index dda4a61a7c..d516479d16 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 739e048339..50b2694daa 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 @@ -99,6 +100,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 260251fb26..40eaf73e3e 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 3de8cfb9ff..d0bbea450b 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).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 e25bf969fc..a4342f3801 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 7833f0a51e..acf123b23e 100644 --- a/testsuite/MDAnalysisTests/topology/base.py +++ b/testsuite/MDAnalysisTests/topology/base.py @@ -100,3 +100,11 @@ def test_creates_universe(self, filename): """Check that Universe works with this Parser""" 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