From f9747b14db9abb02a1e0137b63627d2ed4deb9e9 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 26 Oct 2025 23:10:28 -0400 Subject: [PATCH 1/4] chore: loading diffpy.srxconfutils from pypi instead of local --- pyproject.toml | 2 +- requirements/conda.txt | 1 + requirements/pip.txt | 1 + src/diffpy/confutils/__init__.py | 19 - src/diffpy/confutils/config.py | 820 ------------------------------- src/diffpy/confutils/tools.py | 209 -------- src/diffpy/confutils/version.py | 28 -- 7 files changed, 3 insertions(+), 1077 deletions(-) delete mode 100644 src/diffpy/confutils/__init__.py delete mode 100644 src/diffpy/confutils/config.py delete mode 100644 src/diffpy/confutils/tools.py delete mode 100644 src/diffpy/confutils/version.py diff --git a/pyproject.toml b/pyproject.toml index 0e8f3cb..82caca3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ authors = [ maintainers = [ { name="Simon Billinge", email="sb2896@columbia.edu" }, ] -description = "Distance Printer, calculate the inter atomic distances. Part of xPDFsuite" +description = "Srxplanar processes data from 2D detectors before propagating it to PDFgetX3" keywords = ['diffpy', 'pdf', 'data interpretation'] readme = "README.rst" requires-python = ">=3.11, <3.14" diff --git a/requirements/conda.txt b/requirements/conda.txt index 3b5f87d..8789986 100644 --- a/requirements/conda.txt +++ b/requirements/conda.txt @@ -3,3 +3,4 @@ scipy fabio tifffile configparser +diffpy.srxconfutils \ No newline at end of file diff --git a/requirements/pip.txt b/requirements/pip.txt index 3b5f87d..9db6f8e 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -3,3 +3,4 @@ scipy fabio tifffile configparser +diffpy.srxconfutils diff --git a/src/diffpy/confutils/__init__.py b/src/diffpy/confutils/__init__.py deleted file mode 100644 index a3af0ba..0000000 --- a/src/diffpy/confutils/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.confutils by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2010-2025 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Xiaohao Yang -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## - -# package version -from diffpy.srxplanar.version import __version__ # noqa: F401 - -# End of file diff --git a/src/diffpy/confutils/config.py b/src/diffpy/confutils/config.py deleted file mode 100644 index faefc9a..0000000 --- a/src/diffpy/confutils/config.py +++ /dev/null @@ -1,820 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.confutils by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2012-2025 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Xiaohao Yang -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## -"""Package for organizing program configurations. It can read/write -configurations file, parse arguments from command lines, and also parse -arguments passed from method/function calling inside python. - -Note: for python 2.6, argparse and orderedDict is required, install them with -easy_install -""" - - -import argparse -import os -from collections import OrderedDict -from configparser import ConfigParser - -from diffpy.confutils.tools import FakeConfigFile, StrConv, opt2Str, str2Opt - - -class ConfigBase(object): - """_optdatalist_default, _optdatalist are metadata used to - initialize the options, see below for examples. - - options presents in --help (in cmd), config file, - headers have same order as in these list, - so arrange them in right order here. - - optional args to control if the options presents in args, config file or - file header - - 'args' - default is 'a' - if 'a', this option will be available in self.args - if 'n', this option will not be available in self.args - 'config' - default is 'a' - if 'f', this option will present in self.config and be written to - config file only in full mode - if 'a', this option will present in self.config and be written to - config file both in full and short mode - if 'n', this option will not present in self.config - 'header' - default is 'a' - if 'f', this option will be written to header only in full mode - if 'a', this option will be written to header both in full and short - mode - if 'n', this option will not be written to header - - so in short mode, all options with 'a' will be written, in full mode, - all options with 'a' or 'f' will be written - """ - - # Text to display before the argument help - _description = """Description of configurations - """ - # Text to display after the argument help - _epilog = """ - """ - - """ - optdata contains these keys: - these args will be passed to argparse, see the documents of argparse for - detail information - - 'f': full, (positional) - 's': short - 'h': help - 't': type - 'a': action - 'n': nargs - 'd': default - 'c': choices - 'r': required - 'de': dest - 'co': const - """ - _optdatanamedict = { - "h": "help", - "t": "type", - "a": "action", - "n": "nargs", - "d": "default", - "c": "choices", - "r": "required", - "de": "dest", - "co": "const", - } - - # examples, overload it - _optdatalist_default = [ - [ - "configfile", - { - "sec": "Control", - "config": "f", - "header": "n", - "s": "c", - "h": "name of input config file", - "d": "", - }, - ], - [ - "createconfig", - { - "sec": "Control", - "config": "n", - "header": "n", - "h": ( - "create a config file according to default" - "or current values" - ), - "d": "", - }, - ], - [ - "createconfigfull", - { - "sec": "Control", - "config": "n", - "header": "n", - "h": "create a full configurable config file", - "d": "", - }, - ], - ] - # examples, overload it - _optdatalist = [ - [ - "tifdirectory", - { - "sec": "Experiment", - "header": "n", - "s": "tifdir", - "h": "directory of raw tif files", - "d": "currentdir", - }, - ], - [ - "integrationspace", - { - "sec": "Experiment", - "h": "integration space, could be twotheta or qspace", - "d": "twotheta", - "c": ["twotheta", "qspace"], - }, - ], - [ - "wavelength", - { - "sec": "Experiment", - "h": "wavelength of x-ray, in A", - "d": 0.1000, - }, - ], - [ - "rotationd", - { - "sec": "Experiment", - "s": "rot", - "h": "rotation angle of tilt plane, in degree", - "d": 0.0, - }, - ], - [ - "includepattern", - { - "sec": "Beamline", - "s": "ipattern", - "h": "file name pattern for included files", - "n": "*", - "d": ["*.tif"], - }, - ], - [ - "excludepattern", - { - "sec": "Beamline", - "s": "epattern", - "h": "file name pattern for excluded files", - "n": "*", - "d": ["*.dark.tif", "*.raw.tif"], - }, - ], - [ - "fliphorizontal", - { - "sec": "Beamline", - "h": "flip the image horizontally", - "n": "?", - "co": True, - "d": False, - }, - ], - [ - "regulartmatrixenable", - { - "sec": "Others", - "h": "normalize tmatrix in splitting method", - "n": "?", - "co": True, - "d": False, - }, - ], - [ - "maskedges", - { - "sec": "Others", - "config": "f", - "header": "f", - "h": ( - "mask the edge pixels, first four means" - " the number of pixels masked in each edge \n" - " (left, right, top, bottom)," - " the last one is the radius of a region" - " masked around the corner" - ), - "n": 5, - "d": [1, 1, 1, 1, 50], - }, - ], - ] - - # some default data - # configfile: default config file name - # headertitle: default title of header - _defaultdata = { - "configfile": ["config.cfg"], - "headertitle": "Configuration information", - } - - def __init__(self, filename=None, args=None, **kwargs): - """Init the class and update the values of options if specified - in filename/args/kwargs. - - it will: - 1. call self._preInit method - 2. find the config file if specified in filename/args/kwargs - if failed, try to find default config file - 3. update the options value using filename/args/kwargs - file > args > kwargs - - :param filename: str, file name of the config file - :param args: list of str, args passed from cmd - :param kwargs: dict, optional kwargs - - :return: None - """ - # call self._preInit - self._preInit(**kwargs) - - # update config, first detect if a default config should be load - filename = self._findDefaultConfigFile(filename, args, **kwargs) - self.updateConfig(filename, args, **kwargs) - return - - # example, overload it - def _preInit(self, **kwargs): - """Method called in init process, overload it! - - this method will be called before reading config from - file/args/kwargs - """ - # for name in ['rotation']: - # setattr(self.__class__, name, _configPropertyRad(name+'d')) - # self._configlist['Experiment'].extend(['rotation']) - return - - ########################################################################### - - def _findConfigFile(self, filename=None, args=None, **kwargs): - """Find config file, if any config is specified in - filename/args/kwargs then return the filename of config. - - :param filename: str, file name of config file - :param filename: list of str, args passed from cmd - :param kwargs: optional kwargs - :return: name of config file if found, otherwise None - """ - rv = None - if filename is not None: - rv = filename - if args is not None: - if ("--configfile" in args) or ("-c" in args): - obj = self.args.parse_args(args) - rv = obj.configfile - if kwargs.has_key("configfile"): - rv = kwargs["configfile"] - return rv - - def _findDefaultConfigFile(self, filename=None, args=None, **kwargs): - """Find default config file, if any config is specified in - filename/args/kwargs or in self._defaultdata['configfile'], then - return the filename of config. - - kwargs > args > filename > default - - param filename: str, file name of config file - param filename: list of str, args passed from cmd - param kwargs: optional kwargs - - return: name of config file if found, otherwise None - """ - rv = self._findConfigFile(filename, args, **kwargs) - if rv is None: - for dconf in self._defaultdata["configfile"]: - if (os.path.exists(dconf)) and (rv is None): - rv = dconf - return rv - - ########################################################################### - - def _updateSelf(self, optnames=None, **kwargs): - """Update the options value, then copy the values in the - self.'options' to self.config. - - 1. call self._preUpdateSelf - 2. apply options' value from *self.option* to self.config - 3. call self._postUpdateSelf - - :param optnames: str or list of str, name of options whose value has - been changed, if None, update all options - """ - # so some check right here - self._preUpdateSelf(**kwargs) - # copy value to self.config - self._copySelftoConfig(optnames) - # so some check right here - self._postUpdateSelf(**kwargs) - return - - # example, overload it - def _preUpdateSelf(self, **kwargs): - """Additional process called in self._updateSelf, this method is - called before self._copySelftoConfig(), i.e. before copy options - value to self.config (config file)""" - return - - def _postUpdateSelf(self, **kwargs): - """Additional process called in self._updateSelf, this method is - called after self._copySelftoConfig(), i.e. before copy options - value to self.config (config file)""" - return - - ########################################################################### - - def _getTypeStr(self, optname): - """Return the type of option. - - :param optname: str, name of option - :return: string, type of the option - """ - opttype = self._getTypeStrC(optname) - return opttype - - @classmethod - def _getTypeStrC(cls, optname): - """Class method, return the type of option first try to get type - information from metadata, if failed, try to get type from - default value. - - :param optname: str, name of option - :return: string, type of the option - """ - optdata = cls._optdata[optname] - if "t" in optdata: - opttype = optdata["t"] - else: - value = optdata["d"] - if isinstance(value, str): - opttype = "str" - elif isinstance(value, bool): - opttype = "bool" - elif isinstance(value, float): - opttype = "float" - elif isinstance(value, int): - opttype = "int" - elif isinstance(value, list): - if len(value) == 0: - opttype = "strlist" - elif isinstance(value[0], str): - opttype = "strlist" - elif isinstance(value[0], bool): - opttype = "boollist" - elif isinstance(value[0], float): - opttype = "floatlist" - elif isinstance(value[0], int): - opttype = "intlist" - - return opttype - - ########################################################################### - - def _detectAddSections(self): - """Detect sections present in self._optdata and add them to - self.config also add it to self._configlist.""" - self._detectAddSectionsC(self) - return - - @classmethod - def _detectAddSectionsC(cls): - """Class method, detect sections present in self._optdata and - add them to self.config also add it to self._configlist.""" - # seclist = [self._optdata[key]['sec'] for key in self._optdata.keys()] - seclist = [cls._optdata[opt[0]]["sec"] for opt in cls._optdatalist] - secdict = OrderedDict.fromkeys(seclist) - # for sec in set(seclist): - for sec in secdict.keys(): - cls.config.add_section(sec) - cls._configlist[sec] = [] - return - - def _addOpt(self, optname): - """Add options to self.config and self.args and self.*option*, - this will read metadata from self._optdatalist. - - :param optname: string, name of option - """ - self._addOptC(self, optname) - return - - @classmethod - def _addOptC(cls, optname): - """Class method, add options to self.config and self.args and - self.*option*, this will read metadata in self._optdatalist. - - :param optname: string, name of option - """ - optdata = cls._optdata[optname] - opttype = cls._getTypeStrC(optname) - - # replace currentdir in default to os.getcwd() - if optdata["d"] == "currentdir": - optdata["d"] = os.getcwd() - - # add to cls.'optname' - cls._addOptSelfC(optname, optdata) - - # add to cls.config - secname = optdata["sec"] if "sec" in optdata else "Others" - cls._configlist[secname].append(optname) - if optdata.get("config", "a") != "n": - strvalue = ( - ", ".join(map(str, optdata["d"])) - if isinstance(optdata["d"], list) - else str(optdata["d"]) - ) - cls.config.set(secname, optname, strvalue) - # add to cls.args - if optdata.get("args", "a") != "n": - # transform optdata to a dict that can pass to add_argument method - pargs = dict() - for key in optdata.keys(): - if key in cls._optdatanamedict: - pargs[cls._optdatanamedict[key]] = optdata[key] - pargs["default"] = argparse.SUPPRESS - pargs["type"] = StrConv(opttype) - # add args - if "f" in optdata: - cls.args.add_argument(optname, **pargs) - elif "s" in optdata: - cls.args.add_argument( - "--" + optname, "-" + optdata["s"], **pargs - ) - else: - cls.args.add_argument("--" + optname, **pargs) - return - - @classmethod - def _addOptSelfC(cls, optname, optdata): - """Class method, assign options value to *self.option*, using - metadata. - - :param optname: string, name of the option - :param optdata: dict, metadata of the options, get it from - self._optdatalist - """ - setattr(cls, optname, optdata["d"]) - return - - def _copyConfigtoSelf(self, optnames=None): - """Copy the options' value from self.config to self.*option* - - :param optnames: str or list of str, names of options whose - value copied from self.config to self.*option*'. Set None to - update all - """ - if optnames is not None: - optnames = optnames if isinstance(optnames, list) else [optnames] - else: - optnames = [] - for secname in self.config.sections(): - optnames += self.config.options(secname) - - for optname in optnames: - if self._optdata.has_key(optname): - secname = self._optdata[optname]["sec"] - opttype = self._getTypeStr(optname) - optvalue = self.config.get(secname, optname) - setattr(self, optname, str2Opt(opttype, optvalue)) - return - - def _copySelftoConfig(self, optnames=None): - """Copy the value from self.*option* to self.config. - - :param optname: str or list of str, names of options whose value - copied from self.*option* to self.config. Set None to update - all - """ - if optnames is not None: - optnames = optnames if isinstance(optnames, list) else [optnames] - else: - optnames = [] - for secname in self.config.sections(): - optnames += self.config.options(secname) - - for optname in optnames: - if self._optdata.has_key(optname): - secname = self._optdata[optname]["sec"] - opttype = self._getTypeStr(optname) - optvalue = getattr(self, optname) - self.config.set(secname, optname, opt2Str(opttype, optvalue)) - return - - ########################################################################### - - def parseArgs(self, pargs): - """Parse args and update the value in self.*option*, this will - call the self.args() to parse args, - - :param pargs: list of string, arguments to parse, usually coming - from sys.argv - """ - obj = self.args.parse_args(pargs) - changedargs = obj.__dict__.keys() - for optname in changedargs: - if self._optdata.has_key(optname): - setattr(self, optname, getattr(obj, optname)) - # update self - if len(changedargs) > 0: - self._updateSelf(changedargs) - return obj - - def parseKwargs(self, **kwargs): - """Update self.*option* values according to the kwargs. - - :param kwargs: dict, keywords=value - """ - if kwargs != {}: - changedargs = [] - for optname, optvalue in kwargs.iteritems(): - if self._optdata.has_key(optname): - setattr(self, optname, optvalue) - changedargs.append(optname) - # update self - self._updateSelf(changedargs) - return - - def parseConfigFile(self, filename): - """Read a config file and update the self.*option* - - :param filename: str, file name of config file (include path) - """ - if filename is not None: - filename = os.path.abspath(filename) - if os.path.exists(filename): - self.configfile = filename - self._copySelftoConfig() - fileobj = FakeConfigFile(filename) - # self.config.read(filename) - self.config.readfp(fileobj) - self._copyConfigtoSelf() - self._updateSelf() - return - - def updateConfig(self, filename=None, args=None, **kwargs): - """Update config according to a config file, args (from - ``sys.argv``), or ``**kwargs``. - - Steps: - 1. call ``self._preUpdateConfig()`` - 2. process file/args/kwargs passed to this method - 3. read a config file if specified in ``args`` or ``kwargs`` - 4. call ``self._postUpdateConfig()`` - 5. write config file if specified in ``args``/``kwargs`` - - :param filename: str, file name of the config file - :param args: list of str, args passed from cmd - :param kwargs: dict, optional keyword arguments - :return: True if anything updated, False if nothing updated - """ - - # call self._preUpdateConfig - self._preUpdateConfig(**kwargs) - - filename = self._findConfigFile(filename, args, **kwargs) - if filename is not None: - rv = self.parseConfigFile(filename) - if args is not None: - rv = self.parseArgs(args) - if kwargs != {}: - rv = self.parseKwargs(**kwargs) - - if ( - (filename is None) - and ((args is None) or (args == [])) - and (kwargs == {}) - ): - rv = self._updateSelf() - - # call self._callbackUpdateConfig - self._postUpdateConfig(**kwargs) - - # write config file - self._createConfigFile() - return rv - - def _preUpdateConfig(self, **kwargs): - """Method called before parsing args or kwargs or config file, - in self.updateConfig.""" - return - - def _postUpdateConfig(self, **kwargs): - """Method called after parsing args or kwargs or config file, in - self.updateConfig.""" - return - - ########################################################################### - def _createConfigFile(self): - """Write output config file if specified in configuration the - filename is specified by self.createconfig.""" - if (self.createconfig != "") and (self.createconfig is not None): - self.writeConfig(self.createconfig, "short") - self.createconfig = "" - if (self.createconfigfull != "") and ( - self.createconfigfull is not None - ): - self.writeConfig(self.createconfigfull, "full") - self.createconfigfull = "" - return - - def writeConfig(self, filename, mode="short", changeconfigfile=True): - """Write config to file. the file is compatible with python - package ConfigParser. - - :param filename: string, name of file - :param mode: string, 'short' or 'full' ('s' or 'f'). in short - mode, all options with 'a' will be written, in full mode, - all options with 'a' or 'f' will be written - """ - if changeconfigfile: - self.configfile = os.path.abspath(filename) - self._updateSelf() - # func decide if write the option to config according to mode - # options not present in self._optdata will not be written to config - if mode.startswith("s"): - mcond = ( - lambda optname: self._optdata.get( - optname, {"config": "n"} - ).get("config", "a") - == "a" - ) - else: - mcond = ( - lambda optname: self._optdata.get( - optname, {"config": "n"} - ).get("config", "a") - != "n" - ) - - lines = [] - for section in self.config._sections: - tlines = [] - for key, value in self.config._sections[section].items(): - if (key != "__name__") and mcond(key): - tlines.append( - "%s = %s" % (key, str(value).replace("\n", "\n\t")) - ) - if len(tlines) > 0: - lines.append("[%s]" % section) - lines.extend(tlines) - lines.append("") - rv = "\n".join(lines) + "\n" - fp = open(filename, "w") - fp.write(rv) - fp.close() - return - - def getHeader(self, title=None, mode="full"): - """Get a header of configurations values, - - :param title: str, title of header, if None, try to get it from - self.defaultvalue - :param mode: string, 'short' or 'full' ('s' or 'f'). in short - mode, all options with 'a' will be written, in full mode, - all options with 'a' or 'f' will be written - :return: string, lines with line break that can be directly - written to a text file - """ - - lines = [] - title = "# %s #" % ( - self._defaultdata["headertitle"] if title is None else title - ) - lines.append(title) - # func decide if write the option to header according to mode - # options not present in self._optdata will not be written to header - if mode.startswith("s"): - mcond = ( - lambda optname: self._optdata.get( - optname, {"header": "n"} - ).get("header", "a") - == "a" - ) - else: - mcond = ( - lambda optname: self._optdata.get( - optname, {"header": "n"} - ).get("header", "a") - != "n" - ) - - for secname in self._configlist.keys(): - tlines = [] - for optname in self._configlist[secname]: - if mcond(optname): - value = getattr(self, optname) - ttype = self._getTypeStr(optname) - strvalue = ( - ", ".join(map(str, value)) - if ttype.endswith("list") - else str(value) - ) - tlines.append("%s = %s" % (optname, strvalue)) - if len(tlines) > 0: - lines.append("[%s]" % secname) - lines.extend(tlines) - lines.append("") - rv = "\n".join(lines) + "\n" - return rv - - def resetDefault(self, optnames=None): - """Reset all values to their default value. - - :param optnames: list of str, name of options to reset, None for - all options - """ - if optnames is None: - optnames = self._optdata.keys() - for optname in optnames: - if self._optdata.has_key(optname): - setattr(self, optname, self._optdata[optname]["d"]) - self._updateSelf() - return - - ########################################################################### - # IMPORTANT call this method if you want to add options - # as class attributes!!! - - @classmethod - def initConfigClass(cls): - """Init config class and add options to class. - - IMPORTANT: call this method after you define - the metadata of your config class to add options as class attributes!!! - """ - cls._preInitConfigClass() - - cls.config = ConfigParser(dict_type=OrderedDict) - cls.args = argparse.ArgumentParser( - description=cls._description, - epilog=cls._epilog, - formatter_class=argparse.RawDescriptionHelpFormatter, - ) - cls._configlist = OrderedDict({}) - - cls._optdatalist = cls._optdatalist_default + cls._optdatalist - cls._optdata = dict(cls._optdatalist) - cls._detectAddSectionsC() - for opt in cls._optdatalist: - key = opt[0] - cls._addOptC(key) - - cls._postInitConfigClass() - return - - @classmethod - def _postInitConfigClass(cls): - """Additional processes called after initConfigClass. - - overload it - """ - pass - - @classmethod - def _preInitConfigClass(cls): - """Additional processes called before initConfigClass. - - overload it - """ - pass - - -# VERY IMPORTANT!!! -# add options to class -# initConfigClass(ConfigBase) -# ConfigBase.initConfigClass() - -if __name__ == "__main__": - - test = ConfigBase() - test.updateConfig() diff --git a/src/diffpy/confutils/tools.py b/src/diffpy/confutils/tools.py deleted file mode 100644 index 63277d6..0000000 --- a/src/diffpy/confutils/tools.py +++ /dev/null @@ -1,209 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.confutils by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2013-2025 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Xiaohao Yang -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## - -import hashlib -import re -import time -import zlib - -import numpy as np - - -def _configPropertyRad(nm): - """Helper function of options delegation, rad to degree.""" - rv = property( - fget=lambda self: np.radians(getattr(self, nm)), - fset=lambda self, val: setattr(self, nm, np.degrees(val)), - fdel=lambda self: delattr(self, nm), - ) - return rv - - -def _configPropertyR(name): - """Create a property that forwards self.name to self.config.name. - - read only - """ - rv = property( - fget=lambda self: getattr(self.config, name), - doc="attribute forwarded to self.config, read-only", - ) - return rv - - -def _configPropertyRW(name): - """Create a property that forwards self.name to self.config.name. - - read and write - """ - rv = property( - fget=lambda self: getattr(self.config, name), - fset=lambda self, value: setattr(self.config, name, value), - fdel=lambda self: delattr(self, name), - doc="attribute forwarded to self.config, read/write", - ) - return rv - - -def str2bool(v): - """Turn string to bool.""" - return v.lower() in ("yes", "true", "t", "1") - - -def opt2Str(opttype, optvalue): - """Turn the value of one option to string, according to the option - type list of values are turned into "value1, value2, value3...". - - :param opttype: string, type of options, for example 'str' or - 'intlist' - :param optvalue: value of the option - :return: string, usually stored in ConfigBase.config - """ - - if opttype.endswith("list"): - rv = ", ".join(map(str, optvalue)) - else: - rv = str(optvalue) - return rv - - -def StrConv(opttype): - """Get the type (a converter function) according to the opttype. - - the function doesn't take list - - :param opttype: string, a type of options, could be 'str', 'int', - 'float', or 'bool' - :return: type (converter function) - """ - if opttype.startswith("str"): - conv = str - elif opttype.startswith("int"): - conv = int - elif opttype.startswith("float"): - conv = float - elif opttype.startswith("bool"): - conv = str2bool - else: - conv = None - return conv - - -def str2Opt(opttype, optvalue): - """Convert the string to value of one option, according to the - option type. - - :param opttype: string, type of options, for example 'str' or - 'intlist' - :param optvalue: string, value of the option - :return: value of the option, usually stored in ConfigBase.config - """ - # base converter - conv = StrConv(opttype) - if opttype.endswith("list"): - temp = re.split(r"\s*,\s*", optvalue) - rv = map(conv, temp) if len(temp) > 0 else [] - else: - rv = conv(optvalue) - return rv - - -class FakeConfigFile(object): - """A fake configfile object used in reading config from header of - data or a real config file.""" - - def __init__(self, configfile, endline="###"): - self.configfile = configfile - self.fp = open(configfile) - self.endline = endline - self.ended = False - self.name = configfile - return - - def readline(self): - """Readline function.""" - line = self.fp.readline() - if line.startswith(self.endline) or self.ended: - rv = "" - self.ended = True - else: - rv = line - return rv - - def close(self): - """Close the file.""" - self.fp.close() - return - - -def checkCRC32(filename): - """Calculate the crc32 value of file. - - :param filename: path to the file - :return: crc32 value of file - """ - try: - fd = open(filename, "rb") - except Exception: - return "Read error" - eachLine = fd.readline() - prev = 0 - while eachLine: - prev = zlib.crc32(eachLine, prev) - eachLine = fd.readline() - fd.close() - return prev - - -def checkMD5(filename, blocksize=65536): - """Calculate the MD5 value of file. - - :param filename: path to the file - :return: md5 value of file - """ - try: - fd = open(filename, "rb") - except Exception: - return "Read error" - buf = fd.read(blocksize) - md5 = hashlib.md5() - while len(buf) > 0: - md5.update(buf) - buf = fd.read(blocksize) - fd.close() - return md5.hexdigest() - - -def checkFileVal(filename): - """Check file integrity using crc32 and md5. It will read file twice - then compare the crc32 and md5. If two results doesn't match, it - will wait until the file is completed written to disk. - - :param filename: path to the file - """ - valflag = False - lastcrc = checkCRC32(filename) - while not valflag: - currcrc = checkCRC32(filename) - if currcrc == lastcrc: - lastmd5 = checkMD5(filename) - time.sleep(0.01) - currmd5 = checkMD5(filename) - if lastmd5 == currmd5: - valflag = True - else: - time.sleep(0.5) - lastcrc = checkCRC32(filename) - return diff --git a/src/diffpy/confutils/version.py b/src/diffpy/confutils/version.py deleted file mode 100644 index be10460..0000000 --- a/src/diffpy/confutils/version.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env python -############################################################################## -# -# diffpy.confutils by DANSE Diffraction group -# Simon J. L. Billinge -# (c) 2013-2025 Trustees of the Columbia University -# in the City of New York. All rights reserved. -# -# File coded by: Xiaohao Yang -# -# See AUTHORS.txt for a list of people who contributed. -# See LICENSE.txt for license information. -# -############################################################################## -"""Definition of __version__, __date__, __gitsha__.""" - -# We do not use the other three variables, but can be added back if needed. -# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] - -# obtain version information -from importlib.metadata import PackageNotFoundError, version - -try: - __version__ = version("diffpy.srxplanar") -except PackageNotFoundError: - __version__ = "unknown" - -# End of file From 95a5cca625641ec525f09b55d7f17c8eadd7da1c Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 26 Oct 2025 23:13:35 -0400 Subject: [PATCH 2/4] news --- news/srxconfutils.rst | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 news/srxconfutils.rst diff --git a/news/srxconfutils.rst b/news/srxconfutils.rst new file mode 100644 index 0000000..3318c39 --- /dev/null +++ b/news/srxconfutils.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* Remove local clone of srxconfutils and replace with package from pypi + +**Fixed:** + +* + +**Security:** + +* From cf6298ac6211a038924c8695681b0f0ab66a2f07 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Sun, 26 Oct 2025 23:24:00 -0400 Subject: [PATCH 3/4] changing imports to diffpy.srxconfutils to match new structure --- docs/source/api/diffpy.confutils.rst | 34 ------------------------- docs/source/api/diffpy.rst | 1 - src/diffpy/srxplanar/calculate.py | 2 +- src/diffpy/srxplanar/loadimage.py | 2 +- src/diffpy/srxplanar/mask.py | 2 +- src/diffpy/srxplanar/saveresults.py | 2 +- src/diffpy/srxplanar/srxplanarconfig.py | 6 ++--- 7 files changed, 7 insertions(+), 42 deletions(-) delete mode 100644 docs/source/api/diffpy.confutils.rst diff --git a/docs/source/api/diffpy.confutils.rst b/docs/source/api/diffpy.confutils.rst deleted file mode 100644 index ab7ad9d..0000000 --- a/docs/source/api/diffpy.confutils.rst +++ /dev/null @@ -1,34 +0,0 @@ -confutils Package -================= - -:mod:`diffpy.confutils` Package -------------------------------- - -.. automodule:: diffpy.confutils - :members: - :undoc-members: - :show-inheritance: - -:mod:`diffpy.confutils.config` Module -------------------------------------- - -.. automodule:: diffpy.confutils.config - :members: - :undoc-members: - :show-inheritance: - -:mod:`diffpy.confutils.tools` Module ------------------------------------- - -.. automodule:: diffpy.confutils.tools - :members: - :undoc-members: - :show-inheritance: - -:mod:`diffpy.confutils.version` Module --------------------------------------- - -.. automodule:: diffpy.confutils.version - :members: - :undoc-members: - :show-inheritance: diff --git a/docs/source/api/diffpy.rst b/docs/source/api/diffpy.rst index dadd2c0..c21fe22 100644 --- a/docs/source/api/diffpy.rst +++ b/docs/source/api/diffpy.rst @@ -3,5 +3,4 @@ diffpy Package .. toctree:: - diffpy.confutils diffpy.srxplanar diff --git a/src/diffpy/srxplanar/calculate.py b/src/diffpy/srxplanar/calculate.py index 05469e9..13dc66a 100644 --- a/src/diffpy/srxplanar/calculate.py +++ b/src/diffpy/srxplanar/calculate.py @@ -16,7 +16,7 @@ import numpy as np import scipy.ndimage.filters as snf -from diffpy.confutils.tools import _configPropertyR +from diffpy.srxconfutils.tools import _configPropertyR class Calculate(object): diff --git a/src/diffpy/srxplanar/loadimage.py b/src/diffpy/srxplanar/loadimage.py index b30e97e..4c1d7f7 100644 --- a/src/diffpy/srxplanar/loadimage.py +++ b/src/diffpy/srxplanar/loadimage.py @@ -19,7 +19,7 @@ import numpy as np -from diffpy.confutils.tools import _configPropertyR +from diffpy.srxconfutils.tools import _configPropertyR try: import fabio diff --git a/src/diffpy/srxplanar/mask.py b/src/diffpy/srxplanar/mask.py index e4aa188..7dc45d1 100644 --- a/src/diffpy/srxplanar/mask.py +++ b/src/diffpy/srxplanar/mask.py @@ -40,7 +40,7 @@ def openImage(im): import scipy.ndimage.filters as snf import scipy.ndimage.morphology as snm -from diffpy.confutils.tools import _configPropertyR +from diffpy.srxconfutils.tools import _configPropertyR class Mask(object): diff --git a/src/diffpy/srxplanar/saveresults.py b/src/diffpy/srxplanar/saveresults.py index 6d3bdbe..55f3e59 100644 --- a/src/diffpy/srxplanar/saveresults.py +++ b/src/diffpy/srxplanar/saveresults.py @@ -17,7 +17,7 @@ import numpy as np -from diffpy.confutils.tools import _configPropertyR +from diffpy.srxconfutils.tools import _configPropertyR class SaveResults(object): diff --git a/src/diffpy/srxplanar/srxplanarconfig.py b/src/diffpy/srxplanar/srxplanarconfig.py index 14dbcef..0f8b5fe 100644 --- a/src/diffpy/srxplanar/srxplanarconfig.py +++ b/src/diffpy/srxplanar/srxplanarconfig.py @@ -15,8 +15,8 @@ import numpy as np -from diffpy.confutils.config import ConfigBase -from diffpy.confutils.tools import _configPropertyRad +from diffpy.srxconfutils.config import ConfigBase +from diffpy.srxconfutils.tools import _configPropertyRad _description = """ SrXplanar -- integrate 2D powder diffraction image to 1D @@ -505,7 +505,7 @@ class SrXplanarConfig(ConfigBase): - """Config class, based on ConfigBase class in diffpy.confutils.""" + """Config class, based on ConfigBase class in diffpy.srxconfutils.""" # Text to display before the argument help _description = _description From d278a95a8faae4740c21aec2e27d8808b151b9a8 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Mon, 27 Oct 2025 09:21:03 -0400 Subject: [PATCH 4/4] fix: remove srxconfutils from conda.txt for now, add as pip install --- .github/workflows/build-wheel-release-upload.yml | 3 +++ .github/workflows/matrix-and-codecov-on-merge-to-main.yml | 3 +++ .github/workflows/tests-on-pr.yml | 2 ++ requirements/conda.txt | 1 - 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index 9763d03..ba11489 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -13,6 +13,9 @@ jobs: project: diffpy.srxplanar c_extension: false maintainer_GITHUB_username: sbillinge + run: | + pip install diffpy.srxconfutils + secrets: PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }} PAT_TOKEN: ${{ secrets.PAT_TOKEN }} diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml index d36ff9f..c2bf183 100644 --- a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -17,5 +17,8 @@ jobs: project: diffpy.srxplanar c_extension: false headless: false + run: | + pip install diffpy.srxconfutils + secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml index f55914f..ff1914c 100644 --- a/.github/workflows/tests-on-pr.yml +++ b/.github/workflows/tests-on-pr.yml @@ -12,5 +12,7 @@ jobs: c_extension: false headless: false python_version: 3.13 + run: | + pip install diffpy.srxconfutils secrets: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/requirements/conda.txt b/requirements/conda.txt index 8789986..3b5f87d 100644 --- a/requirements/conda.txt +++ b/requirements/conda.txt @@ -3,4 +3,3 @@ scipy fabio tifffile configparser -diffpy.srxconfutils \ No newline at end of file