Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
0dbca13
addition of tests for str and pathlib handling of SingleFrameReaderBa…
talagayev Mar 27, 2024
c2c7c2c
renamed tests to specify GRO input as testcase
talagayev Mar 27, 2024
05ea8de
Added testcase of LAMMPS str and pathlib handling in class SingleFram…
talagayev Mar 27, 2024
5c0383c
Update AUTHORS list
talagayev Mar 27, 2024
9a4932b
Update CHANGELOG
talagayev Mar 27, 2024
ff2a832
Adjusting blank lines for PEP8
talagayev Mar 27, 2024
d85defc
adjsuting blank lines for PEP8
talagayev Mar 27, 2024
87f7a95
removed singleframereader test in test_gro.py
talagayev Apr 15, 2024
27e9685
removed singleframe_reader test in test_lammps.py
talagayev Apr 15, 2024
567e6ef
added test for str and path input for singleframereader in base.py
talagayev Apr 15, 2024
5558dda
Added Baseframe test for singleframes str and path input in base.py
talagayev Apr 15, 2024
2953310
Merge pull request #2 from talagayev/talagayev-patch-1_singleframe
talagayev Apr 15, 2024
c181021
Merge branch 'develop' into singleframereader_pathlib
talagayev Apr 15, 2024
e06ae55
add pathlib checking to topology and universe
hmacdope May 27, 2024
0aa40cc
add test for topology Pathlib and fix changelog
hmacdope May 27, 2024
5dafd01
fix spacing chaneg in AUTHORS
hmacdope May 27, 2024
33d6b4e
Merge branch 'develop' into singleframereader_pathlib
orbeckst Aug 25, 2024
f785eea
Merge branch 'develop' into singleframereader_pathlib
orbeckst Dec 17, 2024
5d0dfe2
Merge branch 'develop' into singleframereader_pathlib
talagayev Nov 17, 2025
bd2c347
Update base.py
talagayev Nov 17, 2025
33280ee
Merge branch 'develop' into singleframereader_pathlib
talagayev Nov 17, 2025
beea20b
Update base.py
talagayev Nov 17, 2025
677eb36
Update base.py
talagayev Nov 17, 2025
95e5ff6
Update base.py
talagayev Nov 17, 2025
aab41b3
Update base.py
talagayev Nov 17, 2025
09787eb
Update base.py
talagayev Nov 17, 2025
b8a2f03
Update base.py
talagayev Nov 17, 2025
02375bd
Update base.py
talagayev Nov 17, 2025
644a6f2
Update base.py
talagayev Nov 18, 2025
d821dfd
Update base.py
talagayev Nov 18, 2025
d0a7c16
Update base.py
talagayev Nov 18, 2025
7c734a3
fix topology error and adjust style
talagayev Nov 18, 2025
e5bb67d
added itp folder recognition and additional asserts
talagayev Nov 18, 2025
cb41888
Update base.py
talagayev Nov 18, 2025
af86339
black formatting
talagayev Nov 18, 2025
27acebe
Merge branch 'singleframereader_pathlib' of https://github.com/talaga…
talagayev Nov 18, 2025
ac7bcc7
Update CHANGELOG
talagayev Nov 18, 2025
6d181ac
Update test_imd.py
talagayev Nov 18, 2025
fe96f36
Update base.py
talagayev Nov 18, 2025
af0ea55
Update base.py
talagayev Nov 18, 2025
df58b43
Update test_imd.py
talagayev Nov 18, 2025
10fa0ec
Update test_imd.py
talagayev Nov 19, 2025
1fa4d16
Update base.py
talagayev Nov 19, 2025
54cbf7a
black formatting
talagayev Nov 19, 2025
a2efb86
Merge branch 'develop' into singleframereader_pathlib
talagayev Nov 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package/CHANGELOG
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ Enhancements
(Issue #4679, PR #4745)

Changes
* Addition of Pathlib object handling for Universe, SingleFrameReaderBase
and Toplogy parsers (Issue #3937, PR#4535)
* The msd.py inside analysis is changed, and ProgressBar is implemented inside
_conclude_simple and _conclude_fft functions instead of tqdm (Issue #5144, PR #5153)

Expand Down
21 changes: 18 additions & 3 deletions package/MDAnalysis/coordinates/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
import abc
import numpy as np
import numbers
import os
import warnings
from typing import Any, Union, Optional, List, Dict

Expand Down Expand Up @@ -1482,8 +1483,11 @@ def __init__(self, filename, convert_units=True, **kwargs):

if isinstance(filename, NamedStream):
self.filename = filename
elif isinstance(filename, (str, bytes, os.PathLike)):
self.filename = os.fspath(filename) # handles Path → str
else:
self.filename = str(filename)
# Leave non-path objects (decoders, structures, etc.) alone
self.filename = filename
self.convert_units = convert_units

ts_kwargs = {}
Expand Down Expand Up @@ -1667,7 +1671,18 @@ 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

# Real filename types we’re okay with normalizing
elif isinstance(filename, (str, bytes, os.PathLike)):
# os.fspath handles Path objects correctly
self.filename = os.fspath(filename)

# Everything else (e.g. mmtf.MMTFDecoder, ParmEd structures, etc.)
# must be kept as-is so parsers can detect them by type.
else:
self.filename = filename
self.convert_units = convert_units

self.n_frames = 1
Expand Down Expand Up @@ -2310,4 +2325,4 @@ def step(self):
Step size for iteration. Always a positive integer greater than 0.

"""
return self._step
return self._step
4 changes: 4 additions & 0 deletions package/MDAnalysis/core/universe.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import contextlib
import collections
import inspect
import pathlib

import MDAnalysis
import sys
Expand Down Expand Up @@ -219,6 +220,9 @@ def _check_file_like(topology):
else:
_name = None
return NamedStream(topology, _name)

elif isinstance(topology, pathlib.Path):
return str(topology)
return topology


Expand Down
9 changes: 8 additions & 1 deletion package/MDAnalysis/topology/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import itertools
import numpy as np
import warnings
import pathlib

from .. import _PARSERS, _PARSER_HINTS
from ..coordinates.base import IOBase
Expand Down Expand Up @@ -120,7 +121,13 @@ class TopologyReaderBase(IOBase, metaclass=_Topologymeta):
"""

def __init__(self, filename):
self.filename = filename
if isinstance(filename, util.NamedStream): # Cover NamedStream case
self.filename = filename

elif isinstance(filename, pathlib.Path): # Cover Pathlib case
self.filename = str(filename)
else:
self.filename = filename # Cover remainder of cases

def parse(self, **kwargs): # pragma: no cover
raise NotImplementedError("Override this in each subclass")
Expand Down
20 changes: 20 additions & 0 deletions testsuite/MDAnalysisTests/coordinates/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

import numpy as np
import pytest
from pathlib import Path
from unittest import TestCase
from numpy.testing import (
assert_equal,
Expand All @@ -35,6 +36,7 @@

import MDAnalysis as mda
from MDAnalysis.coordinates.timestep import Timestep
from MDAnalysis.coordinates.memory import MemoryReader
from MDAnalysis.transformations import translate


Expand Down Expand Up @@ -171,6 +173,12 @@ def test_pickle_singleframe_reader(self):
"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):
Expand Down Expand Up @@ -667,6 +675,18 @@ def test_timeseries_atomgroup_asel_mutex(self, reader):
atomgroup=atoms, asel=atoms, order="fac"
)

def test_pathlib_input(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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here adding an additional Line:
path = path.as_posix())

Fixes the following Failed Pytest in 4 cases:
AttributeError: 'PosixPath' object has no attribute 'encode'

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):
def test_last_frame(self, ref, reader):
Expand Down
1 change: 0 additions & 1 deletion testsuite/MDAnalysisTests/coordinates/test_gro.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@
assert_equal,
)
import pytest
from io import StringIO


class TestGROReaderOld(RefAdK):
Expand Down
21 changes: 21 additions & 0 deletions testsuite/MDAnalysisTests/coordinates/test_imd.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import sys
from types import ModuleType
from weakref import ref
from pathlib import Path
from MDAnalysis.topology.MinimalParser import MinimalParser

import pytest
import numpy as np
Expand Down Expand Up @@ -323,6 +325,25 @@ def test_volume(self, ref, reader):
# to limited floating point precision.
assert_allclose(vol, ref.volume, rtol=0, atol=1.5e0)

def test_pathlib_input(self, ref, reader):
"""
IMDReader cannot deduce n_atoms from the IMD URL, so we bypass
Universe(...) and directly test MinimalParser with both
str and pathlib.Path inputs, passing n_atoms explicitly.
"""
imd_uri = reader.filename
path = Path(imd_uri)

with MinimalParser(imd_uri) as p:
top_str = p.parse(n_atoms=ref.n_atoms)

with MinimalParser(path) as p:
top_path = p.parse(n_atoms=ref.n_atoms)

assert top_str.n_atoms == ref.n_atoms
assert top_path.n_atoms == ref.n_atoms
assert top_str.n_atoms == top_path.n_atoms

def test_reload_auxiliaries_from_description(self, ref, reader):
pytest.skip("Cannot create two IMDReaders on the same stream")

Expand Down
19 changes: 19 additions & 0 deletions testsuite/MDAnalysisTests/topology/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
# J. Comput. Chem. 32 (2011), 2319--2327, doi:10.1002/jcc.21787
#
import pytest
import pathlib

import MDAnalysis as mda
from MDAnalysis.core.topology import Topology
Expand Down Expand Up @@ -123,3 +124,21 @@ def test_guessed_attributes(self, filename):
for attr in self.guessed_attrs:
assert hasattr(u.atoms, attr)
assert attr in u_guessed_attrs

def test_pathlib_input(self, filename):
"""Check that pathlib.Path objects are accepted by the parser."""
if not isinstance(filename, (str, pathlib.Path)):
pytest.skip(
f"Pathlib input test only applies to string/path-like filenames, "
f"got {type(filename).__name__}"
) # Cover OpenMM Parser case
path = pathlib.Path(filename)

with self.parser(filename) as p:
top_str = p.parse()
with self.parser(path) as p:
top_path = p.parse()

assert top_str.n_atoms == top_path.n_atoms
assert top_str.n_residues == top_path.n_residues
assert top_str.n_segments == top_path.n_segments
12 changes: 12 additions & 0 deletions testsuite/MDAnalysisTests/topology/test_itp.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,18 @@ def test_sequential(self, universe):
)
assert_equal(universe.atoms.chargegroups[-1], 63)

def test_pathlib_input(self, filename):
path = Path(filename)

with self.parser(filename) as p:
top_str = p.parse(include_dir=GMX_DIR)
with self.parser(path) as p:
top_path = p.parse(include_dir=GMX_DIR)

assert top_str.n_atoms == top_path.n_atoms
assert top_str.n_residues == top_path.n_residues
assert top_str.n_segments == top_path.n_segments


class TestErrors:

Expand Down
Loading