From de843cf1e6057c670f24e51396121f3fd59c481a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 20:14:01 -0400 Subject: [PATCH 01/21] deprecate PDFParser --- src/diffpy/srfit/pdf/pdfparser.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/diffpy/srfit/pdf/pdfparser.py b/src/diffpy/srfit/pdf/pdfparser.py index 75d9c81f..bfb10a15 100644 --- a/src/diffpy/srfit/pdf/pdfparser.py +++ b/src/diffpy/srfit/pdf/pdfparser.py @@ -28,6 +28,19 @@ from diffpy.srfit.exceptions import ParseError from diffpy.srfit.fitbase.profileparser import ProfileParser +from diffpy.utils._deprecator import build_deprecation_message, deprecated + +removal_verison = "4.0.0" +base = "diffpy.srfit.pdf.pdfparser.PDFParser" +new_base = "diffpy.srfit.fitbase.ProfileParser" + +parseFile_dep_msg = build_deprecation_message( + base, + "parseFile", + "parse_file", + removal_version=removal_verison, + new_base=new_base, +) class PDFParser(ProfileParser): @@ -106,6 +119,11 @@ class PDFParser(ProfileParser): _format = "PDF" + # Marking this function as deprecated because PDFParser.parseFile calls it + # so when people use PDFParser.parseFile, they will get a + # warning that it is deprecated and they should use + # ProfileParser.parse_file instead. + @deprecated(parseFile_dep_msg) def parseString(self, patstring): """Parse a string and set the _x, _y, _dx, _dy and _meta variables. @@ -121,6 +139,7 @@ def parseString(self, patstring): Raises ParseError if the string cannot be parsed """ + # useful regex patterns: rx = {"f": r"[-+]?(\d+(\.\d*)?|\d*\.\d+)([eE][-+]?\d+)?"} # find where does the data start From 567c8a1af9185a4793a321cc12130293c1c22f1f Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 20:17:24 -0400 Subject: [PATCH 02/21] build parse_file and deprecate getNumBank, selectBank, getFormat, getData, and getMetaData --- src/diffpy/srfit/fitbase/profileparser.py | 287 +++++++++++++++++++--- 1 file changed, 255 insertions(+), 32 deletions(-) diff --git a/src/diffpy/srfit/fitbase/profileparser.py b/src/diffpy/srfit/fitbase/profileparser.py index b152164b..7260a66d 100644 --- a/src/diffpy/srfit/fitbase/profileparser.py +++ b/src/diffpy/srfit/fitbase/profileparser.py @@ -23,7 +23,56 @@ """ +from pathlib import Path + +import numpy as np + from diffpy.srfit.exceptions import ParseError +from diffpy.utils._deprecator import build_deprecation_message, deprecated +from diffpy.utils.parsers import load_data + +removal_verison = "4.0.0" +pdfparser_base = "diffpy.srfit.pdf.pdfparser.PDFParser" +new_base = "diffpy.srfit.fitbase.ProfileParser" + + +parseFile_dep_msg = build_deprecation_message( + pdfparser_base, + "parseFile", + "parse_file", + removal_verison, + new_base=new_base, +) + +pp_base = "diffpy.srfit.fitbase.profileparser.ProfileParser" + +getNumBanks_dep_msg = build_deprecation_message( + pp_base, + "getNumBanks", + "get_num_banks", + removal_verison, +) + +selectBank_dep_msg = build_deprecation_message( + pp_base, + "selectBank", + "select_bank", + removal_verison, +) + +getData_dep_msg = build_deprecation_message( + pp_base, + "getData", + "get_data", + removal_verison, +) + +getMetaData_dep_msg = build_deprecation_message( + pp_base, + "getMetaData", + "get_metadata", + removal_verison, +) class ProfileParser(object): @@ -31,48 +80,47 @@ class ProfileParser(object): Attributes ---------- - _format - Name of the data format that this parses (string, default - ""). The format string is a unique identifier for the data + _format : str, optional + The name of the data format that this parses (string, default + `""`). The format string is a unique identifier for the data format handled by the parser. - _banks + _banks : list of tuples The data from each bank. Each bank contains a (x, y, dx, dy) tuple: - x - A numpy array containing the independent - variable read from the file. - y - A numpy array containing the profile + x : np.ndarray + The independent variable read from the file. + y : np.ndarray + The dependent variable (profile) read from the file. - dx - A numpy array containing the uncertainty in x - read from the file. This is None if the + dx : np.ndarray + The uncertainties associated with x + read from the file. This is 0 if the + uncertainty cannot be read. + dy : np.ndarray + The uncertainties associated with y + read from the file. This is 0 if the uncertainty cannot be read. - dy - A numpy array containing the uncertainty read - from the file. This is None if the uncertainty - cannot be read. - _x + _x : np.ndarray Independent variable from the chosen bank - _y + _y : np.ndarray Profile from the chosen bank - _dx + _dx : np.ndarray Uncertainty in independent variable from the chosen bank - _dy + _dy : np.ndarray Uncertainty in profile from the chosen bank - _meta + _meta : dict A dictionary containing metadata read from the file. General Metadata ---------------- - filename + filename : str or Path The name of the file from which data was parsed. This key will not exist if data was not read from file. - nbanks + nbanks : int The number of banks parsed. - bank + bank : int The chosen bank number. """ @@ -110,6 +158,8 @@ def parseString(self, patstring): """ raise NotImplementedError() + # remove parseString too when this file is removed. + @deprecated(parseFile_dep_msg) def parseFile(self, filename): """Parse a file and set the _x, _y, _dx, _dy and _meta variables. @@ -135,14 +185,151 @@ def parseFile(self, filename): if len(self._banks) < 1: raise ParseError("There are no data in the banks") - self.selectBank(0) + self.select_bank(0) return - def getNumBanks(self): - """Get the number of banks read by the parser.""" + def parse_file(self, filename, column_format=None): + """Parse a data file and extract data and metadata with + automatic uncertainty detection. + + - For files with 2 columns: assumes (x, y) and sets dx, dy to 0. + - For files with 3 columns: assumes (x, y, dy) and sets dx to 0. + - For files with 4 columns: assumes (x, y, dx, dy). + - For other cases: `column_format` must be explicitly specified. + + Uncertainty columns (dx, dy) are only considered valid if all values + are positive and not NaN/Inf. Otherwise they are set to 0. + + This wipes out the currently loaded data and selected bank number. + + Parameters + ---------- + filename : str or Path + The name of the file to parse. + column_format : tuple of str, optional + The order in which columns appear in the file. + If None, the format is auto-detected based on the + number of columns. + + Valid labels: `"x"`, `"y"`, `"dx"`, `"dy"` + + Examples: + + - `("x", "y")` + - `("x", "y", "dy")` + - `("x", "y", "dx", "dy")` + - `("x", "dx", "y", "dy")` + + Raises + ------ + ParseError + If parsing fails or ambiguity detected. + """ + # Reset internal state + self._banks = [] + if isinstance(filename, Path): + filename = str(filename) + # Load metadata and numeric data + self._meta, data = self._load_file(filename) + column_format = self._detect_column_format(data, column_format) + # Map columns to x, y, dx, dy + columns = self._map_column_labels_to_data(data, column_format) + # Extract required arrays + x = columns["x"] + y = columns["y"] + x_length = len(x) + y_length = len(y) + dx = self._validate_uncertainty(columns.get("dx"), x_length) + dy = self._validate_uncertainty(columns.get("dy"), y_length) + # Store as single bank + self._banks = [(x, y, dx, dy)] + self._meta["nbanks"] = 1 + self.select_bank(0) + + # --- Private helpers --- # + + def _load_file(self, filename): + """Load metadata and numeric data from a file.""" + meta = load_data(filename, headers=True) + meta["filename"] = filename + data = load_data(filename) + if data.size == 0 or (data.ndim == 1): + raise ParseError( + "Data block must have at least two columns (x, y)." + ) + return meta, data + + def _detect_column_format(self, data, column_format): + """Auto-detect or validate column format.""" + num_cols = data.shape[1] + + if column_format is None: + if num_cols == 2: + column_format = ("x", "y") + elif num_cols == 3: + column_format = ("x", "y", "dy") + elif num_cols == 4: + column_format = ("x", "y", "dx", "dy") + else: + raise ParseError( + f"Expected 2 to 4 columns but found {num_cols}." + ) + if len(column_format) != num_cols: + raise ParseError( + f"column_format has {len(column_format)} " + f"labels but file contains {num_cols} columns." + ) + if len(set(column_format)) != len(column_format): + raise ParseError("column_format cannot contain duplicate labels.") + for label in column_format: + if label not in {"x", "y", "dx", "dy"}: + raise ParseError( + f"column_format contains invalid label '{label}'. " + "Valid labels are 'x', 'y', 'dx', and 'dy'." + ) + return column_format + + def _map_column_labels_to_data(self, data, column_format): + """Map numeric data to columns by label.""" + columns = {} + for i, label in enumerate(column_format): + columns[label] = data[:, i] + + if "x" not in columns or "y" not in columns: + raise ParseError( + "Both 'x' and 'y' columns must be present in the data." + ) + + return columns + + @staticmethod + def _validate_uncertainty(data, length): + """Return the uncertainty data if valid, otherwise 0.""" + if data is None or not np.all(np.isfinite(data)) or np.any(data <= 0): + return np.zeros(length) + return data + + def get_num_banks(self): + """Get the number of banks read by the parser. + + Returns + ------- + int + The number of banks read by the parser. + """ return len(self._banks) - def selectBank(self, index): + @deprecated(getNumBanks_dep_msg) + def getNumBanks(self): + """This function is deprecated and will be removed in version + 4.0.0. + + Please use diffpy.srfit.fitbase.ProfileParser.get_num_banks + instead. + """ + return self.get_num_banks() + + def select_bank(self, index): """Select which bank to use. This method should only be called after the data has been parsed. The @@ -160,7 +347,7 @@ def selectBank(self, index): if index is None: index = self._meta.get("bank", 0) - numbanks = self.getNumBanks() + numbanks = self.get_num_banks() if index > numbanks: raise IndexError("Bank index out of range") @@ -175,7 +362,18 @@ def selectBank(self, index): self._x, self._y, self._dx, self._dy = self._banks[index] return - def getData(self, index=None): + @deprecated(selectBank_dep_msg) + def selectBank(self, index): + """This function is deprecated and will be removed in version + 4.0.0. + + Please use diffpy.srfit.fitbase.ProfileParser.select_bank + instead. + """ + self.select_bank(index) + return + + def get_data(self, index=None): """Get the data. This method should only be called after the data has been parsed. The @@ -192,12 +390,37 @@ def getData(self, index=None): This returns (x, y, dx, dy) tuple for the bank. dx is 0 if it cannot be determined from the data format. """ - self.selectBank(index) + self.select_bank(index) return self._x, self._y, self._dx, self._dy + @deprecated(getData_dep_msg) + def getData(self, index=None): + """This function is deprecated and will be removed in version + 4.0.0. + + Please use diffpy.srfit.fitbase.ProfileParser.get_data instead. + """ + return self.get_data(index) + + def get_metadata(self): + """Get the parsed metadata. + + Returns + ------- + dict + A dictionary containing metadata read from the file. + """ + return self._meta + + @deprecated(getMetaData_dep_msg) def getMetaData(self): - """Get the parsed metadata.""" + """This function is deprecated and will be removed in version + 4.0.0. + + Please use diffpy.srfit.fitbase.ProfileParser.get_metadata + instead. + """ return self._meta From 172fced6e50219fe21b3e99652110c9d005eec8d Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 20:17:52 -0400 Subject: [PATCH 03/21] add ProfileParser to __init__ so it can be imported from fitbase --- src/diffpy/srfit/fitbase/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/diffpy/srfit/fitbase/__init__.py b/src/diffpy/srfit/fitbase/__init__.py index bdbcb2d1..ce68fa7d 100644 --- a/src/diffpy/srfit/fitbase/__init__.py +++ b/src/diffpy/srfit/fitbase/__init__.py @@ -39,6 +39,7 @@ "Profile", "ProfileGenerator", "SimpleRecipe", + "ProfileParser", ] from diffpy.srfit.fitbase.calculator import Calculator @@ -48,6 +49,7 @@ from diffpy.srfit.fitbase.fitresults import FitResults, initializeRecipe from diffpy.srfit.fitbase.profile import Profile from diffpy.srfit.fitbase.profilegenerator import ProfileGenerator +from diffpy.srfit.fitbase.profileparser import ProfileParser from diffpy.srfit.fitbase.simplerecipe import SimpleRecipe # End of file From 9680c770be7160ff78dbd6fdd4fa9645243aeb38 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 20:18:23 -0400 Subject: [PATCH 04/21] add test files as conftest fixture --- tests/conftest.py | 99 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 6eb797c3..25714656 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -269,3 +269,102 @@ def temp_data_files(tmp_path): """ ) yield tmp_path + + +@pytest.fixture +def parser_datafile(tmp_path): + """Create temporary data files with different column layouts and + yield the directory.""" + + METADATA_HEADER = r"""# xPDFsuite Configuration # +[PDF] +wavelength = 0.1 +dataformat = QA +inputfile = input.iq +backgroundfile = backgroundfile.iq +mode = xray +bgscale = 1.0 +composition = TiSe2 +outputtype = gr +qmaxinst = 25.0 +qmin = 0.1 +qmax = 25.0 +rmax = 140.0 +rmin = 0.0 +rstep = 0.01 +rpoly = 0.7 + +[Misc] +inputdir = /my/data/dir +savedir = /my/save/dir +backgroundfilefull = /my/data/dir/backgroundfile.iq + +#### start data +#S 1 +""" + + # Four-column standard + (tmp_path / "four_col.gr").write_text( + METADATA_HEADER + + r"""#L r($\AA$) G($\AA^{-2}$) dr($\AA$) dG($\AA^{-2}$) +1.0 2.0 0.1 0.2 +1.1 2.1 0.3 0.4 +1.2 2.2 0.5 0.6""" + ) + + # Three-column (x, y, dy) + (tmp_path / "three_col.dat").write_text( + METADATA_HEADER + + r"""#L r($\AA$) G($\AA^{-2}$) dG($\AA^{-2}$) +1.0 2.0 0.2 +1.1 2.1 0.4 +1.2 2.2 0.6""" + ) + + # Two-column (x, y) + (tmp_path / "two_col.txt").write_text( + METADATA_HEADER + + r"""#L r($\AA$) G($\AA^{-2}$) +1.0 2.0 +1.1 2.1 +1.2 2.2""" + ) + + # Four-column reordered (x, dx, y, dy) + (tmp_path / "four_col_reordered.txt").write_text( + METADATA_HEADER + + r"""#L r($\AA$) dr($\AA$) G($\AA^{-2}$) dG($\AA^{-2}$) +1.0 0.1 2.0 0.2 +1.1 0.3 2.1 0.4 +1.2 0.5 2.2 0.6""" + ) + + # Four-column with NaN/Inf + (tmp_path / "four_col_nan_inf.gr").write_text( + METADATA_HEADER + + r"""#L r($\AA$) G($\AA^{-2}$) dr($\AA$) dG($\AA^{-2}$) +1.0 2.0 nan inf +1.1 2.1 inf 1 +1.2 2.2 nan nan""" + ) + + # One-column + (tmp_path / "one_col.gr").write_text( + METADATA_HEADER + + r"""#L r($\AA$) +1.0 +1.1 +1.2""" + ) + + # Five-column (extra column) + (tmp_path / "five_col.gr").write_text( + METADATA_HEADER + + r"""#L r($\AA$) G($\AA^{-2}$) dr($\AA$) dG($\AA^{-2}$) extra +1.0 2.0 0.1 0.2 9.9 +1.1 2.1 0.3 0.4 9.8 +1.2 2.2 0.5 0.6 9.7""" + ) + + # Yield the directory + yield tmp_path From 5568579d0b7cd22581b513c421d782ff716b74bd Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 20:19:05 -0400 Subject: [PATCH 05/21] use ProfileParser in PDFContribution, replacing PDFParser --- src/diffpy/srfit/pdf/pdfcontribution.py | 26 +++++++------------------ 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/src/diffpy/srfit/pdf/pdfcontribution.py b/src/diffpy/srfit/pdf/pdfcontribution.py index c884641b..73e666ea 100644 --- a/src/diffpy/srfit/pdf/pdfcontribution.py +++ b/src/diffpy/srfit/pdf/pdfcontribution.py @@ -20,7 +20,7 @@ __all__ = ["PDFContribution"] -from diffpy.srfit.fitbase import FitContribution, Profile +from diffpy.srfit.fitbase import FitContribution, Profile, ProfileParser class PDFContribution(FitContribution): @@ -105,28 +105,16 @@ def __init__(self, name): # Data methods - def loadData(self, data): - """Load the data in various formats. - - This uses the PDFParser to load the data and then passes it to the - built-in profile with load_parsed_data. + def loadData(self, datafile): + """Load the data from a datafile. Parameters ---------- - data - An open file-like object, name of a file that contains data - or a string containing the data. + data : str or Path + The path to the data file. """ - # Get the data into a string - from diffpy.srfit.util.inpututils import inputToString - - datstr = inputToString(data) - - # Load data with a PDFParser - from diffpy.srfit.pdf.pdfparser import PDFParser - - parser = PDFParser() - parser.parseString(datstr) + parser = ProfileParser() + parser.parse_file(datafile) # Pass it to the profile self.profile.load_parsed_data(parser) From 937e11f3b3b4415a265f750aa53c60e6f69af035 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 20:21:03 -0400 Subject: [PATCH 06/21] Use ProfileParser instead of PDFParser for fitrecipe testing --- tests/test_fitrecipe.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_fitrecipe.py b/tests/test_fitrecipe.py index 4da38ab8..b697f531 100644 --- a/tests/test_fitrecipe.py +++ b/tests/test_fitrecipe.py @@ -23,7 +23,7 @@ from numpy import array_equal, dot, linspace, pi, sin from scipy.optimize import leastsq -from diffpy.srfit.fitbase import FitResults +from diffpy.srfit.fitbase import FitResults, ProfileParser from diffpy.srfit.fitbase.fitcontribution import FitContribution from diffpy.srfit.fitbase.fitrecipe import FitRecipe from diffpy.srfit.fitbase.parameter import Parameter @@ -655,8 +655,8 @@ def build_recipe_from_datafile(datafile): """Helper to build a FitRecipe from a datafile using PDFParser and PDFGenerator.""" profile = Profile() - parser = PDFParser() - parser.parseFile(str(datafile)) + parser = ProfileParser() + parser.parse_file(str(datafile)) profile.load_parsed_data(parser) contribution = FitContribution("c") From 97cfc58cae8a7188761d3b227d9372a025d2b1fe Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 20:21:52 -0400 Subject: [PATCH 07/21] getData --> get_data, and getMetaData --> get_metadata in Profile --- src/diffpy/srfit/fitbase/profile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/srfit/fitbase/profile.py b/src/diffpy/srfit/fitbase/profile.py index bc598d3f..0262935d 100644 --- a/src/diffpy/srfit/fitbase/profile.py +++ b/src/diffpy/srfit/fitbase/profile.py @@ -159,8 +159,8 @@ def load_parsed_data(self, parser): This sets the xobs, yobs, dyobs arrays as well as the metadata. """ - x, y, junk, dy = parser.getData() - self.meta = dict(parser.getMetaData()) + x, y, junk, dy = parser.get_data() + self.meta = dict(parser.get_metadata()) self.set_observed_profile(x, y, dy) return From 9544243fab0f02f4dcba71cf41dce89df2ccc60a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 20:33:16 -0400 Subject: [PATCH 08/21] add set_parsed_profile test with ProfileParser --- src/diffpy/srfit/fitbase/profile.py | 2 +- tests/test_profile.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/diffpy/srfit/fitbase/profile.py b/src/diffpy/srfit/fitbase/profile.py index 0262935d..d66e61f7 100644 --- a/src/diffpy/srfit/fitbase/profile.py +++ b/src/diffpy/srfit/fitbase/profile.py @@ -159,7 +159,7 @@ def load_parsed_data(self, parser): This sets the xobs, yobs, dyobs arrays as well as the metadata. """ - x, y, junk, dy = parser.get_data() + x, y, dx, dy = parser.get_data() self.meta = dict(parser.get_metadata()) self.set_observed_profile(x, y, dy) return diff --git a/tests/test_profile.py b/tests/test_profile.py index f8c186b4..b69f6f7b 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -22,6 +22,7 @@ from numpy import allclose, arange, array, array_equal, ones_like from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase import ProfileParser from diffpy.srfit.fitbase.profile import Profile @@ -307,5 +308,20 @@ def _test(p): return +def test_load_parsed_data(parser_datafiles): + """Test the load_parsed_data method.""" + prof = Profile() + parser = ProfileParser() + datafile = parser_datafiles / "four_col.gr" + parser.parse_file(datafile) + prof.load_parsed_data(parser) + expected_xobs = [1.0, 1.1, 1.2] + expected_yobs = [2.0, 2.1, 2.2] + expected_dyobs = [0.2, 0.4, 0.6] + assert prof.xobs.tolist() == expected_xobs + assert prof.yobs.tolist() == expected_yobs + assert prof.dyobs.tolist() == expected_dyobs + + if __name__ == "__main__": unittest.main() From a616dc2060335a476ae3de3b8b02fbb1932c7980 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 21:01:34 -0400 Subject: [PATCH 09/21] update examples with new parser --- docs/examples/coreshellnp.py | 5 +++-- docs/examples/crystalpdf.py | 12 +++++++----- docs/examples/crystalpdfall.py | 5 +++-- docs/examples/crystalpdfobjcryst.py | 13 +++++++------ docs/examples/crystalpdftwodata.py | 7 ++++--- docs/examples/crystalpdftwophase.py | 5 +++-- docs/examples/nppdfcrystal.py | 5 +++-- docs/examples/nppdfsas.py | 5 +++-- 8 files changed, 33 insertions(+), 24 deletions(-) diff --git a/docs/examples/coreshellnp.py b/docs/examples/coreshellnp.py index 23871a46..8560221d 100644 --- a/docs/examples/coreshellnp.py +++ b/docs/examples/coreshellnp.py @@ -29,8 +29,9 @@ FitRecipe, FitResults, Profile, + ProfileParser, ) -from diffpy.srfit.pdf import PDFGenerator, PDFParser +from diffpy.srfit.pdf import PDFGenerator # Example Code @@ -42,7 +43,7 @@ def makeRecipe(stru1, stru2, datname): profile = Profile() # Load data and add it to the profile - parser = PDFParser() + parser = ProfileParser() parser.parseFile(datname) profile.load_parsed_data(parser) profile.set_calculation_range(xmin=1.5, xmax=45, dx=0.1) diff --git a/docs/examples/crystalpdf.py b/docs/examples/crystalpdf.py index fcfbded3..cdd3472e 100644 --- a/docs/examples/crystalpdf.py +++ b/docs/examples/crystalpdf.py @@ -32,8 +32,9 @@ FitRecipe, FitResults, Profile, + ProfileParser, ) -from diffpy.srfit.pdf import PDFGenerator, PDFParser +from diffpy.srfit.pdf import PDFGenerator from diffpy.structure import Structure ###### @@ -48,12 +49,12 @@ def makeRecipe(ciffile, datname): profile = Profile() # Load data and add it to the Profile. Unlike in other examples, we use a - # class (PDFParser) to help us load the data. This class will read the data - # and relevant metadata from a two- to four-column data file generated + # class (ProfileParser) to help us load the data. This class will read the + # data and relevant metadata from a two- to four-column data file generated # with PDFGetX2 or PDFGetN. The metadata will be passed to the PDFGenerator # when they are associated in the FitContribution, which saves some # configuration steps. - parser = PDFParser() + parser = ProfileParser() parser.parseFile(datname) profile.load_parsed_data(parser) profile.set_calculation_range(xmax=20) @@ -62,7 +63,8 @@ def makeRecipe(ciffile, datname): # The PDFGenerator is for configuring and calculating a PDF profile. Here, # we want to refine a Structure object from diffpy.structure. We tell the # PDFGenerator that with the 'setStructure' method. All other configuration - # options will be inferred from the metadata that is read by the PDFParser. + # options will be inferred from the metadata that is read by the + # ProfileParser. # In particular, this will set the scattering type (x-ray or neutron), the # Qmax value, as well as initial values for the non-structural Parameters. generator = PDFGenerator("G") diff --git a/docs/examples/crystalpdfall.py b/docs/examples/crystalpdfall.py index 1ee8903c..37a57c78 100644 --- a/docs/examples/crystalpdfall.py +++ b/docs/examples/crystalpdfall.py @@ -27,8 +27,9 @@ FitRecipe, FitResults, Profile, + ProfileParser, ) -from diffpy.srfit.pdf import PDFGenerator, PDFParser +from diffpy.srfit.pdf import PDFGenerator ###### # Example Code @@ -37,7 +38,7 @@ def makeProfile(datafile): """Make an place data within a Profile.""" profile = Profile() - parser = PDFParser() + parser = ProfileParser() parser.parseFile(datafile) profile.load_parsed_data(parser) profile.set_calculation_range(xmax=20) diff --git a/docs/examples/crystalpdfobjcryst.py b/docs/examples/crystalpdfobjcryst.py index 672a2267..43a98e4d 100644 --- a/docs/examples/crystalpdfobjcryst.py +++ b/docs/examples/crystalpdfobjcryst.py @@ -28,8 +28,9 @@ FitRecipe, FitResults, Profile, + ProfileParser, ) -from diffpy.srfit.pdf import PDFGenerator, PDFParser +from diffpy.srfit.pdf import PDFGenerator ###### # Example Code @@ -42,11 +43,11 @@ def makeRecipe(ciffile, datname): # This will be used to store the observed and calculated PDF profile. profile = Profile() - # Load data and add it to the Profile. As before we use a PDFParser. The - # metadata is still passed to the PDFGenerator later on. The interaction - # between the PDFGenerator and the metadata does not depend on type of - # structure being refined. - parser = PDFParser() + # Load data and add it to the Profile. As before we use a ProfileParser. + # The metadata is still passed to the PDFGenerator later on. + # The interaction between the PDFGenerator and the metadata does not + # depend on type of structure being refined. + parser = ProfileParser() parser.parseFile(datname) profile.load_parsed_data(parser) profile.set_calculation_range(xmax=20) diff --git a/docs/examples/crystalpdftwodata.py b/docs/examples/crystalpdftwodata.py index 0b0d6093..03458fa8 100644 --- a/docs/examples/crystalpdftwodata.py +++ b/docs/examples/crystalpdftwodata.py @@ -29,8 +29,9 @@ FitRecipe, FitResults, Profile, + ProfileParser, ) -from diffpy.srfit.pdf import PDFGenerator, PDFParser +from diffpy.srfit.pdf import PDFGenerator ###### # Example Code @@ -46,12 +47,12 @@ def makeRecipe(ciffile, xdatname, ndatname): nprofile = Profile() # Load data and add it to the proper Profile. - parser = PDFParser() + parser = ProfileParser() parser.parseFile(xdatname) xprofile.load_parsed_data(parser) xprofile.set_calculation_range(xmax=20) - parser = PDFParser() + parser = ProfileParser() parser.parseFile(ndatname) nprofile.load_parsed_data(parser) nprofile.set_calculation_range(xmax=20) diff --git a/docs/examples/crystalpdftwophase.py b/docs/examples/crystalpdftwophase.py index 1d6878b2..c2dbcf78 100644 --- a/docs/examples/crystalpdftwophase.py +++ b/docs/examples/crystalpdftwophase.py @@ -29,8 +29,9 @@ FitRecipe, FitResults, Profile, + ProfileParser, ) -from diffpy.srfit.pdf import PDFGenerator, PDFParser +from diffpy.srfit.pdf import PDFGenerator ###### # Example Code @@ -43,7 +44,7 @@ def makeRecipe(niciffile, siciffile, datname): profile = Profile() # Load data and add it to the profile - parser = PDFParser() + parser = ProfileParser() parser.parseFile(datname) profile.load_parsed_data(parser) profile.set_calculation_range(xmax=20) diff --git a/docs/examples/nppdfcrystal.py b/docs/examples/nppdfcrystal.py index 95cca7e3..a568e8b9 100644 --- a/docs/examples/nppdfcrystal.py +++ b/docs/examples/nppdfcrystal.py @@ -32,8 +32,9 @@ FitRecipe, FitResults, Profile, + ProfileParser, ) -from diffpy.srfit.pdf import PDFGenerator, PDFParser +from diffpy.srfit.pdf import PDFGenerator def makeRecipe(ciffile, grdata): @@ -42,7 +43,7 @@ def makeRecipe(ciffile, grdata): # Set up a PDF fit as has been done in other examples. pdfprofile = Profile() - pdfparser = PDFParser() + pdfparser = ProfileParser() pdfparser.parseFile(grdata) pdfprofile.load_parsed_data(pdfparser) pdfprofile.set_calculation_range(xmin=0.1, xmax=20) diff --git a/docs/examples/nppdfsas.py b/docs/examples/nppdfsas.py index 7bea8069..2aac74b4 100644 --- a/docs/examples/nppdfsas.py +++ b/docs/examples/nppdfsas.py @@ -31,8 +31,9 @@ FitRecipe, FitResults, Profile, + ProfileParser, ) -from diffpy.srfit.pdf import PDFGenerator, PDFParser +from diffpy.srfit.pdf import PDFGenerator from diffpy.srfit.pdf.characteristicfunctions import SASCF from diffpy.srfit.sas import SASGenerator, SASParser @@ -47,7 +48,7 @@ def makeRecipe(ciffile, grdata, iqdata): # Create a PDF contribution as before pdfprofile = Profile() - pdfparser = PDFParser() + pdfparser = ProfileParser() pdfparser.parseFile(grdata) pdfprofile.load_parsed_data(pdfparser) pdfprofile.set_calculation_range(xmin=0.1, xmax=20) From 515205cedb267c52247fe6a9c6f4570ee40c0e7a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 21:02:41 -0400 Subject: [PATCH 10/21] update test_pdf to reflect new parser --- tests/conftest.py | 2 +- tests/test_pdf.py | 13 +++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 25714656..8d64babf 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -272,7 +272,7 @@ def temp_data_files(tmp_path): @pytest.fixture -def parser_datafile(tmp_path): +def parser_datafiles(tmp_path): """Create temporary data files with different column layouts and yield the directory.""" diff --git a/tests/test_pdf.py b/tests/test_pdf.py index d8e86e54..908d5eb1 100644 --- a/tests/test_pdf.py +++ b/tests/test_pdf.py @@ -23,6 +23,7 @@ import pytest from diffpy.srfit.exceptions import SrFitError +from diffpy.srfit.fitbase import ProfileParser from diffpy.srfit.pdf import PDFContribution, PDFGenerator, PDFParser # ---------------------------------------------------------------------------- @@ -46,7 +47,7 @@ def testParser1(datafile): assert meta.get("scale") is None assert meta.get("doping") is None - x, y, dx, dy = parser.getData() + x, y, dx, dy = parser.get_data() assert dx is None assert dy is None @@ -78,12 +79,12 @@ def testParser1(datafile): def testParser2(datafile): data = datafile("si-q27r60-xray.gr") - parser = PDFParser() - parser.parseFile(data) + parser = ProfileParser() + parser.parse_file(data) meta = parser._meta - assert data == meta["filename"] + assert str(data) == meta["filename"] assert 1 == meta["nbanks"] assert "X" == meta["stype"] assert 27 == meta["qmax"] @@ -94,7 +95,7 @@ def testParser2(datafile): assert meta.get("scale") is None assert meta.get("doping") is None - x, y, dx, dy = parser.getData() + x, y, dx, dy = parser.get_data() testx = numpy.linspace(0.01, 60, 5999, endpoint=False) diff = testx - x res = numpy.dot(diff, diff) @@ -136,7 +137,7 @@ def testParser2(datafile): res = numpy.dot(diff, diff) assert 0 == pytest.approx(res) - assert dx is None + assert dx.tolist() == [0] * len(dx) return From 7eaea4b314a251c772c28d95d15de56396fd9247 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 21:03:26 -0400 Subject: [PATCH 11/21] update old formatted test data to get parser to pass tests --- tests/testdata/si-q27r60-xray.gr | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/testdata/si-q27r60-xray.gr b/tests/testdata/si-q27r60-xray.gr index e25d5795..a0f208ad 100644 --- a/tests/testdata/si-q27r60-xray.gr +++ b/tests/testdata/si-q27r60-xray.gr @@ -9,7 +9,9 @@ sourcedir=C:\Program Files\PDFgetX2\ logfile=.pdfgetx2.log quiet=0 debug=0 autosave_isa=1 savefilenamebase=si325_mesh_300k_nor_4-8 iqfilesurfix=.iq sqfilesurfix=.sq fqfilesurfix=.fq grfilesurfix=.gr - +stype = X +qmax = 27 +temperature = 300 ##### DataFileFormat datatype=1 (0:SPEC, 1:CHI, 2:nxm column, 3:unknown) num_skiplines=3 comment_id=# delimiter= From dd32346514d1a84bb49f3e2e802feae692976382 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 21:06:57 -0400 Subject: [PATCH 12/21] add test for ProfileParser --- tests/test_profileparser.py | 200 ++++++++++++++++++++++++++++++++++++ 1 file changed, 200 insertions(+) create mode 100644 tests/test_profileparser.py diff --git a/tests/test_profileparser.py b/tests/test_profileparser.py new file mode 100644 index 00000000..e1676b15 --- /dev/null +++ b/tests/test_profileparser.py @@ -0,0 +1,200 @@ +import re +from pathlib import Path + +import pytest + +from diffpy.srfit.exceptions import ParseError +from diffpy.srfit.fitbase.profileparser import ProfileParser + +# UC1: User loads file with all x, y, dx, dy columns in that format +# expected: x, y, dx, dy, and metadata are all read correctly +# UC2: User loads file with x, y, dy columns in that format (dx is missing) +# expected: x, y, dy, and metadata are all read correctly +# UC3: User loads file with x, y columns in that format (dx and dy are missing) +# expected: x, y, and metadata are all read correctly +# UC4: User loads file with x, dx, y, dy columns in that format and specifies +# column_format +# expected: x, y, dx, dy, and metadata are all read correctly +# UC5: User loads file with dy and dx values containing NaN and inf values +# expected: x, y, and metadata are all read correctly and dx and dy are set to +# 0 for all values + +# UC6: User loads file with only one column +# expected: ParseError is raised +# UC7: User loads file with 5 columns +# expected: ParseError is raised +# UC8: User loads file with x, y, and dy but specifies column_format with 4 +# columns +# expected: ParseError is raised +# UC9: User loads file with x, y, dx, and dy but specifies column_format with 5 +# columns +# expected: ParseError is raised +# UC10: User loads file with x, y, dx, and dy but specifies column_format with +# 3 columns +# expected: ParseError is raised +# UC11: User loads file with x, y, dx, and dy but specifies column_format with +# duplicate values +# expected: ParseError is raised + +EXPECTED_META = { + "wavelength": 0.1, + "dataformat": "QA", + "inputfile": "input.iq", + "backgroundfile": "backgroundfile.iq", + "mode": "xray", + "bgscale": 1.0, + "composition": "TiSe2", + "outputtype": "gr", + "qmaxinst": 25.0, + "qmin": 0.1, + "qmax": 25.0, + "rmax": 140.0, + "rmin": 0.0, + "rstep": 0.01, + "rpoly": 0.7, + "inputdir": "/my/data/dir", + "savedir": "/my/save/dir", + "backgroundfilefull": "/my/data/dir/backgroundfile.iq", + "nbanks": 1, + "bank": 0, +} + + +@pytest.mark.parametrize( + "input_file, column_order, expected_x, " + "expected_y, expected_dx, expected_dy", + [ + # UC1: 4-column file (x, y, dx, dy) — all columns present + # expected: x, y, dx, dy, and metadata are all read correctly + ( + Path("four_col.gr"), + None, + [1.0, 1.1, 1.2], + [2.0, 2.1, 2.2], + [0.1, 0.3, 0.5], + [0.2, 0.4, 0.6], + ), + # UC2: 3-column file (x, y, dy) — dx is missing + # expected: x, y, dy, and metadata are all read correctly + ( + Path("three_col.dat"), + None, + [1.0, 1.1, 1.2], + [2.0, 2.1, 2.2], + [0.0, 0.0, 0.0], + [0.2, 0.4, 0.6], + ), + # UC3: 2-column file (x, y) — dx and dy are missing + # expected: x, y, and metadata are all read correctly + ( + Path("two_col.txt"), + None, + [1.0, 1.1, 1.2], + [2.0, 2.1, 2.2], + [0.0, 0.0, 0.0], + [0.0, 0.0, 0.0], + ), + # UC4: 4-column file in (x, dx, y, dy) order with explicit + # column_format + # expected: x, y, dx, dy, and metadata are all read correctly + ( + Path("four_col_reordered.txt"), + ("x", "dx", "y", "dy"), + [1.0, 1.1, 1.2], + [2.0, 2.1, 2.2], + [0.1, 0.3, 0.5], + [0.2, 0.4, 0.6], + ), + # UC5: 4-column file where dx/dy contain NaN and inf values + # expected: x, y, and metadata are read correctly; dx and dy + # are set to 0 + ( + Path("four_col_nan_inf.gr"), + None, + [1.0, 1.1, 1.2], + [2.0, 2.1, 2.2], + [0.0, 0.0, 0.0], + [0.0, 0.0, 0.0], + ), + ], +) +def test_parse_file( + parser_datafiles, + input_file, + column_order, + expected_x, + expected_y, + expected_dx, + expected_dy, +): + parser = ProfileParser() + parser.parse_file(parser_datafiles / input_file, column_order) + actual_x = parser._x.tolist() + actual_y = parser._y.tolist() + actual_dx = parser._dx.tolist() + actual_dy = parser._dy.tolist() + actual_metadata = parser._meta + actual_metadata["filename"] = actual_metadata["filename"].split("/")[-1] + + EXPECTED_META["filename"] = str(input_file).split("/")[-1] + assert actual_x == expected_x + assert actual_y == expected_y + assert actual_dx == expected_dx + assert actual_dy == expected_dy + assert actual_metadata == EXPECTED_META + + +@pytest.mark.parametrize( + "input_file, column_order, msg", + [ + # UC6: Only one column — cannot form x/y pair + # expected: ParseError is raised + ( + "one_col.gr", + None, + "Data block must have at least two columns (x, y).", + ), + # UC7: Five columns — ambiguous, no mapping defined + # expected: ParseError is raised + ("five_col.gr", None, "Expected 2 to 4 columns but found 5."), + # UC8: 3-column file but column_format expects 4 columns + # expected: ParseError is raised + ( + "three_col.dat", + ("x", "y", "dx", "dy"), + "column_format has 4 labels but file contains 3 columns.", + ), + # UC9: 4-column file but column_format expects 5 columns + # expected: ParseError is raised + ( + "four_col.gr", + ("x", "y", "dx", "dy", "extra"), + "column_format has 5 labels but file contains 4 columns.", + ), + # UC10: 4-column file but column_format expects only 3 columns + # expected: ParseError is raised + ( + "four_col.gr", + ("x", "y", "dy"), + "column_format has 3 labels but file contains 4 columns.", + ), + # UC11: column_format contains duplicate column names + # expected: ParseError is raised + ( + "four_col.gr", + ("x", "x", "dx", "dy"), + "column_format cannot contain duplicate labels.", + ), + # UC12: column_format contains invalid column names + ( + "four_col.gr", + ("x", "y", "dx", "invalid"), + "column_format contains invalid label 'invalid'. " + "Valid labels are 'x', 'y', 'dx', and 'dy'.", + ), + ], +) +def test_parse_file_bad(parser_datafiles, input_file, column_order, msg): + parser = ProfileParser() + with pytest.raises(ParseError, match=re.escape(msg)): + parser.parse_file(parser_datafiles / input_file, column_order) From ecead8d84c354935010bcb1d34744cb5db649ba7 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 21:07:35 -0400 Subject: [PATCH 13/21] update sas module and test to reflect new changes --- src/diffpy/srfit/sas/sasparser.py | 2 +- tests/test_sas.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/diffpy/srfit/sas/sasparser.py b/src/diffpy/srfit/sas/sasparser.py index 22da76f8..5503a417 100644 --- a/src/diffpy/srfit/sas/sasparser.py +++ b/src/diffpy/srfit/sas/sasparser.py @@ -126,7 +126,7 @@ def parseFile(self, filename): # FIXME: Revisit when we refactor the SAS characteristic functions. # Why is a list imported but only the first element is taken? # Is this desired behavior? - self.selectBank(0) + self.select_bank(0) return def parseString(self, patstring): diff --git a/tests/test_sas.py b/tests/test_sas.py index db856942..33dab04a 100644 --- a/tests/test_sas.py +++ b/tests/test_sas.py @@ -31,7 +31,7 @@ def testParser(sas_available, datafile): data = datafile("sas_ascii_test_1.txt") parser = SASParser() parser.parseFile(data) - x, y, dx, dy = parser.getData() + x, y, dx, dy = parser.get_data() testx = numpy.array( [ 0.002618, From 51d2a43f27d52a84b7bd672210a9caa313cc831f Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 21:10:42 -0400 Subject: [PATCH 14/21] news --- news/profileparser_dep.rst | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 news/profileparser_dep.rst diff --git a/news/profileparser_dep.rst b/news/profileparser_dep.rst new file mode 100644 index 00000000..9f180049 --- /dev/null +++ b/news/profileparser_dep.rst @@ -0,0 +1,29 @@ +**Added:** + +* Add ``parse_file`` method to ``ProfileParser`` to parse a file directly with ``load_data`` from ``diffpy.utils``. +* Add ``get_num_bank`` method to ``ProfileParser`` to replace ``getNumBank``. +* Add ``select_bank`` method to ``ProfileParser`` to replace ``selectBank``. +* Add ``get_format`` method to ``ProfileParser`` to replace ``getFormat``. +* Add ``get_data`` method to ``ProfileParser`` to replace ``getData``. +* Add ``get_meta_data`` method to ``ProfileParser`` to replace ``getMetaData``. + +**Changed:** + +* + +**Deprecated:** + +* Deprecate ``PDFParser``. Use ``ProfileParser`` instead. +* Deprecate ``getNumBank``, ``selectBank``, ``getFormat``, ``getData``, and ``getMetaData`` in ``ProfileParser``. + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* From 62b3af5bb96c3ca0783d9c437f8a3dcf436ed33b Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 21:33:48 -0400 Subject: [PATCH 15/21] rm comment --- src/diffpy/srfit/fitbase/profileparser.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/diffpy/srfit/fitbase/profileparser.py b/src/diffpy/srfit/fitbase/profileparser.py index 7260a66d..16c8e014 100644 --- a/src/diffpy/srfit/fitbase/profileparser.py +++ b/src/diffpy/srfit/fitbase/profileparser.py @@ -189,8 +189,8 @@ def parseFile(self, filename): return def parse_file(self, filename, column_format=None): - """Parse a data file and extract data and metadata with - automatic uncertainty detection. + """Parse a data file to extract data and metadata, with + automatic handling of uncertainties. - For files with 2 columns: assumes (x, y) and sets dx, dy to 0. - For files with 3 columns: assumes (x, y, dy) and sets dx to 0. @@ -246,8 +246,6 @@ def parse_file(self, filename, column_format=None): self._meta["nbanks"] = 1 self.select_bank(0) - # --- Private helpers --- # - def _load_file(self, filename): """Load metadata and numeric data from a file.""" meta = load_data(filename, headers=True) From 653c98d30cf4affade639a759cef22f79daa1be6 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Mon, 16 Mar 2026 21:36:36 -0400 Subject: [PATCH 16/21] blank commit to fix pre-commit From 63aa9e9769e718f38268515239650c9d80230f1d Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 17 Mar 2026 09:26:56 -0400 Subject: [PATCH 17/21] run pre-commit autoupdate and run pre-commit --- .pre-commit-config.yaml | 20 ++++++++++---------- src/diffpy/srfit/equation/builder.py | 1 + src/diffpy/srfit/fitbase/profileparser.py | 1 - src/diffpy/srfit/interface/__init__.py | 1 - src/diffpy/srfit/structure/sgconstraints.py | 1 - src/diffpy/srfit/util/weakrefcallable.py | 3 +-- tests/conftest.py | 19 ++++++------------- tests/debug.py | 1 - tests/run.py | 1 - tests/test_contribution.py | 1 + tests/test_weakrefcallable.py | 1 - 11 files changed, 19 insertions(+), 31 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0e4a84d1..f189a945 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,7 +11,7 @@ ci: submodules: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v6.0.0 hooks: - id: check-yaml - id: end-of-file-fixer @@ -21,45 +21,45 @@ repos: - id: check-toml - id: check-added-large-files - repo: https://github.com/psf/black - rev: 24.4.2 + rev: 26.3.1 hooks: - id: black - repo: https://github.com/pycqa/flake8 - rev: 7.0.0 + rev: 7.3.0 hooks: - id: flake8 - repo: https://github.com/pycqa/isort - rev: 5.13.2 + rev: 8.0.1 hooks: - id: isort args: ["--profile", "black"] - repo: https://github.com/kynan/nbstripout - rev: 0.7.1 + rev: 0.9.1 hooks: - id: nbstripout - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v6.0.0 hooks: - id: no-commit-to-branch name: Prevent Commit to Main Branch args: ["--branch", "main"] stages: [pre-commit] - repo: https://github.com/codespell-project/codespell - rev: v2.3.0 + rev: v2.4.2 hooks: - id: codespell additional_dependencies: - tomli # prettier - multi formatter for .json, .yml, and .md files - repo: https://github.com/pre-commit/mirrors-prettier - rev: f12edd9c7be1c20cfa42420fd0e6df71e42b51ea # frozen: v4.0.0-alpha.8 + rev: v4.0.0-alpha.8 hooks: - id: prettier additional_dependencies: - "prettier@^3.2.4" # docformatter - PEP 257 compliant docstring formatter - - repo: https://github.com/s-weigand/docformatter - rev: 5757c5190d95e5449f102ace83df92e7d3b06c6c + - repo: https://github.com/PyCQA/docformatter + rev: v1.7.7 hooks: - id: docformatter additional_dependencies: [tomli] diff --git a/src/diffpy/srfit/equation/builder.py b/src/diffpy/srfit/equation/builder.py index 0bb81350..a41f6bda 100644 --- a/src/diffpy/srfit/equation/builder.py +++ b/src/diffpy/srfit/equation/builder.py @@ -75,6 +75,7 @@ > beq = c*f(a,b) > eq = beq.makeEquation() """ + import inspect import numbers import token diff --git a/src/diffpy/srfit/fitbase/profileparser.py b/src/diffpy/srfit/fitbase/profileparser.py index 16c8e014..7713abc5 100644 --- a/src/diffpy/srfit/fitbase/profileparser.py +++ b/src/diffpy/srfit/fitbase/profileparser.py @@ -22,7 +22,6 @@ See the class documentation for more information. """ - from pathlib import Path import numpy as np diff --git a/src/diffpy/srfit/interface/__init__.py b/src/diffpy/srfit/interface/__init__.py index 3157c1ec..896ae519 100644 --- a/src/diffpy/srfit/interface/__init__.py +++ b/src/diffpy/srfit/interface/__init__.py @@ -19,7 +19,6 @@ for scripting. """ - from diffpy.srfit.interface.interface import ( FitRecipeInterface, ParameterInterface, diff --git a/src/diffpy/srfit/structure/sgconstraints.py b/src/diffpy/srfit/structure/sgconstraints.py index 666e360d..893c90a0 100644 --- a/src/diffpy/srfit/structure/sgconstraints.py +++ b/src/diffpy/srfit/structure/sgconstraints.py @@ -14,7 +14,6 @@ ############################################################################## """Code to set space group constraints for a crystal structure.""" - import re import numpy diff --git a/src/diffpy/srfit/util/weakrefcallable.py b/src/diffpy/srfit/util/weakrefcallable.py index 042e1812..1cbb87aa 100644 --- a/src/diffpy/srfit/util/weakrefcallable.py +++ b/src/diffpy/srfit/util/weakrefcallable.py @@ -14,7 +14,6 @@ ############################################################################## """Picklable storage of callable objects using weak references.""" - import types import weakref @@ -125,7 +124,7 @@ def __getstate__(self): def __setstate__(self, state): """Restore the weak reference in this wrapper upon unpickling.""" - (self._class, nm, self.fallback, mobj) = state + self._class, nm, self.fallback, mobj = state self.function = getattr(self._class, nm) if mobj is None: # use a fake weak reference that mimics deallocated object. diff --git a/tests/conftest.py b/tests/conftest.py index 8d64babf..23b92f6a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -242,8 +242,7 @@ def temp_data_files(tmp_path): cgr_file.write_text("1.0 2.0\n" "1.1 2.1\n" "1.2 2.2\n") results_file = tmp_path / "fit_results.res" - results_file.write_text( - """ + results_file.write_text(""" Results written: Wed Feb 25 15:14:58 2026 produced by cadenmyers @@ -266,8 +265,7 @@ def temp_data_files(tmp_path): Variable Correlations greater than 25% (Correlations invalid) ------------------------------------------------------------------------------ No correlations greater than 25% -""" - ) +""") yield tmp_path @@ -314,8 +312,7 @@ def parser_datafiles(tmp_path): # Three-column (x, y, dy) (tmp_path / "three_col.dat").write_text( - METADATA_HEADER - + r"""#L r($\AA$) G($\AA^{-2}$) dG($\AA^{-2}$) + METADATA_HEADER + r"""#L r($\AA$) G($\AA^{-2}$) dG($\AA^{-2}$) 1.0 2.0 0.2 1.1 2.1 0.4 1.2 2.2 0.6""" @@ -323,8 +320,7 @@ def parser_datafiles(tmp_path): # Two-column (x, y) (tmp_path / "two_col.txt").write_text( - METADATA_HEADER - + r"""#L r($\AA$) G($\AA^{-2}$) + METADATA_HEADER + r"""#L r($\AA$) G($\AA^{-2}$) 1.0 2.0 1.1 2.1 1.2 2.2""" @@ -349,13 +345,10 @@ def parser_datafiles(tmp_path): ) # One-column - (tmp_path / "one_col.gr").write_text( - METADATA_HEADER - + r"""#L r($\AA$) + (tmp_path / "one_col.gr").write_text(METADATA_HEADER + r"""#L r($\AA$) 1.0 1.1 -1.2""" - ) +1.2""") # Five-column (extra column) (tmp_path / "five_col.gr").write_text( diff --git a/tests/debug.py b/tests/debug.py index 8ebfc4ad..64d5c83f 100644 --- a/tests/debug.py +++ b/tests/debug.py @@ -19,7 +19,6 @@ Exceptions raised by failed tests or other errors are not caught. """ - if __name__ == "__main__": import sys diff --git a/tests/run.py b/tests/run.py index a375045e..9ca3054a 100644 --- a/tests/run.py +++ b/tests/run.py @@ -17,7 +17,6 @@ python -m diffpy.srfit.tests.run """ - if __name__ == "__main__": import sys diff --git a/tests/test_contribution.py b/tests/test_contribution.py index 813ef9c8..56975905 100644 --- a/tests/test_contribution.py +++ b/tests/test_contribution.py @@ -13,6 +13,7 @@ # ############################################################################## """Tests for refinableobj module.""" + import unittest import numpy as np diff --git a/tests/test_weakrefcallable.py b/tests/test_weakrefcallable.py index 1e8ca092..688bc563 100644 --- a/tests/test_weakrefcallable.py +++ b/tests/test_weakrefcallable.py @@ -14,7 +14,6 @@ ############################################################################## """Unit tests for the weakrefcallable module.""" - import pickle import unittest From 985e474e7ac07685752e362908198f64d25a340a Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 17 Mar 2026 09:46:38 -0400 Subject: [PATCH 18/21] set python version to 3.13 to pin pre-commit CI --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f189a945..124954e0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ default_language_version: - python: python3 + python: python3.13 ci: autofix_commit_msg: | [pre-commit.ci] auto fixes from pre-commit hooks From 17c997179dcebaad5a406ee1785bf24800c771f8 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 17 Mar 2026 09:58:31 -0400 Subject: [PATCH 19/21] pin only docformatter to python3.13 --- .pre-commit-config.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 124954e0..d118bebc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,5 @@ default_language_version: - python: python3.13 + python: python3 ci: autofix_commit_msg: | [pre-commit.ci] auto fixes from pre-commit hooks @@ -62,5 +62,6 @@ repos: rev: v1.7.7 hooks: - id: docformatter + language_version: python3.13 additional_dependencies: [tomli] args: [--in-place, --config, ./pyproject.toml] From ab44228edc1e70a0912ef0d0206b9bb578578fcc Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 17 Mar 2026 10:20:26 -0400 Subject: [PATCH 20/21] rm python3.13 pin on pre-commit-config for now --- .pre-commit-config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d118bebc..f189a945 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -62,6 +62,5 @@ repos: rev: v1.7.7 hooks: - id: docformatter - language_version: python3.13 additional_dependencies: [tomli] args: [--in-place, --config, ./pyproject.toml] From ce39171ab5323c332f2f576de91deb3f1cad23e2 Mon Sep 17 00:00:00 2001 From: Caden Myers Date: Tue, 17 Mar 2026 16:39:41 -0400 Subject: [PATCH 21/21] pip pre-commit docformatter to 3.13 --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f189a945..d118bebc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -62,5 +62,6 @@ repos: rev: v1.7.7 hooks: - id: docformatter + language_version: python3.13 additional_dependencies: [tomli] args: [--in-place, --config, ./pyproject.toml]