From 242cbe3cb7fcc69d364c82c34c5a9434a6998191 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 13 Aug 2025 18:34:34 -0400 Subject: [PATCH 01/37] toml --- dpx/confutils/config.py | 2 +- dpx/confutils/configtraits.py | 2 +- pyproject.toml | 85 +++++++++++++++++++++ requirements/pip.txt | 4 + setup.py | 134 ---------------------------------- 5 files changed, 91 insertions(+), 136 deletions(-) create mode 100644 pyproject.toml create mode 100644 requirements/pip.txt delete mode 100755 setup.py diff --git a/dpx/confutils/config.py b/dpx/confutils/config.py index 03f64cc..f784e7f 100644 --- a/dpx/confutils/config.py +++ b/dpx/confutils/config.py @@ -22,7 +22,7 @@ ''' -import ConfigParser +from configparser import ConfigParser import re import os import sys diff --git a/dpx/confutils/configtraits.py b/dpx/confutils/configtraits.py index a4dda2a..d80d04c 100644 --- a/dpx/confutils/configtraits.py +++ b/dpx/confutils/configtraits.py @@ -22,7 +22,7 @@ Note: for python 2.6, argparse and orderedDict is required, install them with easy_install ''' -import ConfigParser +from configparser import ConfigParser import re import os import sys diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..2d09092 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,85 @@ +[build-system] +requires = ["setuptools>=62.0", "setuptools-git-versioning>=2.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "diffpy.srxconfutils" +dynamic=['version', 'dependencies'] +authors = [ + { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, +] +maintainers = [ + { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, +] +description = "Configuration utilities for dpx project. Part of xPDFsuite" +keywords = ['diffpy', 'pdf', 'data interpretation'] +readme = "README.rst" +requires-python = ">=3.11, <3.14" +classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Intended Audience :: Developers', + 'Intended Audience :: Science/Research', + 'License :: OSI Approved :: BSD License', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: Microsoft :: Windows', + 'Operating System :: POSIX', + 'Operating System :: Unix', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', + 'Topic :: Scientific/Engineering :: Physics', + 'Topic :: Scientific/Engineering :: Chemistry', +] + +[project.urls] +Homepage = "https://github.com/diffpy/diffpy.distanceprinter/" +Issues = "https://github.com/diffpy/diffpy.distanceprinter/issues/" + +[tool.setuptools-git-versioning] +enabled = true +template = "{tag}" +dev_template = "{tag}" +dirty_template = "{tag}" + +[tool.setuptools.packages.find] +where = ["diffpy"] # list of folders that contain the packages (["."] by default) +include = ["*"] # package names should match these glob patterns (["*"] by default) +exclude = [] # exclude packages matching these glob patterns (empty by default) +namespaces = false # to disable scanning PEP 420 namespaces (true by default) + +[tool.setuptools.dynamic] +dependencies = {file = ["requirements/pip.txt"]} + +[tool.codespell] +exclude-file = ".codespell/ignore_lines.txt" +ignore-words = ".codespell/ignore_words.txt" +skip = "*.cif,*.dat,*agr" + +[tool.docformatter] +recursive = true +wrap-summaries = 72 +wrap-descriptions = 72 + +[tool.black] +line-length = 79 +include = '\.pyi?$' +exclude = ''' +/( + \.git + | \.hg + | \.mypy_cache + | \.tox + | \.venv + | \.rst + | \.txt + | _build + | buck-out + | build + | dist + + # The following are specific to Black, you probably don't want those. + | blib2to3 + | tests/data +)/ +''' diff --git a/requirements/pip.txt b/requirements/pip.txt new file mode 100644 index 0000000..f8d7a2e --- /dev/null +++ b/requirements/pip.txt @@ -0,0 +1,4 @@ +numpy +configparser +traits +traitsui \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100755 index 57f2e07..0000000 --- a/setup.py +++ /dev/null @@ -1,134 +0,0 @@ -#!/usr/bin/env python - -# Installation script for dpx.confutils - -"""dpx.confutils - configuration utilities for dpx project - -Packages: dpx.confutils -Scripts: None -""" - -import os -from setuptools import setup, find_packages -from setuptools.command.build_py import build_py -import py_compile - - -class custom_build_pyc(build_py): - - def byte_compile(self, files): - for file in files: - if file.endswith('.py'): - py_compile.compile(file) - os.unlink(file) - -# Use this version when git data are not available, like in git zip archive. -# Update when tagging a new release. -FALLBACK_VERSION = '1.0' - -# versioncfgfile holds version data for git commit hash and date. -# It must reside in the same directory as version.py. -MYDIR = os.path.dirname(os.path.abspath(__file__)) -versioncfgfile = os.path.join(MYDIR, 'dpx', 'confutils', 'version.cfg') -gitarchivecfgfile = versioncfgfile.replace('version.cfg', 'gitarchive.cfg') - - -def gitinfo(): - from subprocess import Popen, PIPE - kw = dict(stdout=PIPE, cwd=MYDIR) - proc = Popen(['git', 'describe', '--match=v[[:digit:]]*'], **kw) - desc = proc.stdout.read() - proc = Popen(['git', 'log', '-1', '--format=%H %at %ai'], **kw) - glog = proc.stdout.read() - rv = {} - rv['version'] = '.post'.join(desc.strip().split('-')[:2]).lstrip('v') - rv['commit'], rv['timestamp'], rv['date'] = glog.strip().split(None, 2) - return rv - - -def getversioncfg(): - import re - from ConfigParser import RawConfigParser - vd0 = dict(version=FALLBACK_VERSION, commit='', date='', timestamp=0) - # first fetch data from gitarchivecfgfile, ignore if it is unexpanded - g = vd0.copy() - cp0 = RawConfigParser(vd0) - cp0.read(gitarchivecfgfile) - if '$Format:' not in cp0.get('DEFAULT', 'commit'): - g = cp0.defaults() - mx = re.search(r'\btag: v(\d[^,]*)', g.pop('refnames')) - if mx: - g['version'] = mx.group(1) - # then try to obtain version data from git. - gitdir = os.path.join(MYDIR, '.git') - if os.path.exists(gitdir) or 'GIT_DIR' in os.environ: - try: - g = gitinfo() - except OSError: - pass - # finally, check and update the active version file - cp = RawConfigParser() - cp.read(versioncfgfile) - d = cp.defaults() - rewrite = not d or (g['commit'] and ( - g['version'] != d.get('version') or g['commit'] != d.get('commit'))) - if rewrite: - cp.set('DEFAULT', 'version', g['version']) - cp.set('DEFAULT', 'commit', g['commit']) - cp.set('DEFAULT', 'date', g['date']) - cp.set('DEFAULT', 'timestamp', g['timestamp']) - cp.write(open(versioncfgfile, 'w')) - return cp - -versiondata = getversioncfg() - - -def dirglob(d, *patterns): - from glob import glob - rv = [] - for p in patterns: - rv += glob(os.path.join(d, p)) - return rv - -# define distribution -setup_args = dict( - name='dpx.confutils', - cmdclass=dict(build_py=custom_build_pyc), - version=versiondata.get('DEFAULT', 'version'), - namespace_packages=['dpx'], - packages=find_packages(), - include_package_data=True, - zip_safe=False, - entry_points={ - # define console_scripts here, see setuptools docs for details. - }, - - author='Simon J.L. Billinge', - author_email='sb2896@columbia.edu', - description='configuration utilities for dpx project', - maintainer='Xiaohao Yang', - maintainer_email='sodestiny1@gmail.com', - license='see LICENSENOTICE.txt', - url='', - keywords='dpx configuration utilities', - classifiers=[ - # List of possible values at - # http://pypi.python.org/pypi?:action=list_classifiers - 'Development Status :: 5 - Production/Stable', - 'Environment :: MacOS X', - 'Environment :: Win32 (MS Windows)', - 'Environment :: X11 Applications', - 'Intended Audience :: Science/Research', - 'Operating System :: MacOS', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', - 'Topic :: Scientific/Engineering :: Physics', - ], -) - -if __name__ == '__main__': - setup(**setup_args) - -# End of file From 3877b13f6be8887fc6ed0c62afea6936ce12de11 Mon Sep 17 00:00:00 2001 From: Simon Billinge Date: Wed, 13 Aug 2025 21:13:49 -0400 Subject: [PATCH 02/37] better behaved license --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2d09092..94aa589 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ classifiers = [ 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', - 'License :: OSI Approved :: BSD License', + 'License :: Free To Use But Restricted', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', @@ -33,8 +33,8 @@ classifiers = [ ] [project.urls] -Homepage = "https://github.com/diffpy/diffpy.distanceprinter/" -Issues = "https://github.com/diffpy/diffpy.distanceprinter/issues/" +Homepage = "https://github.com/diffpy/diffpy.srxconfutils/" +Issues = "https://github.com/diffpy/diffpy.srxconfutils/issues/" [tool.setuptools-git-versioning] enabled = true @@ -43,7 +43,7 @@ dev_template = "{tag}" dirty_template = "{tag}" [tool.setuptools.packages.find] -where = ["diffpy"] # list of folders that contain the packages (["."] by default) +where = ["dpx"] # list of folders that contain the packages (["."] by default) include = ["*"] # package names should match these glob patterns (["*"] by default) exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) From 98ae03557f96c4359448ced0f5ed717163301332 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sat, 16 Aug 2025 01:30:46 -0400 Subject: [PATCH 03/37] chore: change dpx to src/diffpy --- {dpx => src/diffpy}/__init__.py | 0 {dpx => src/diffpy}/confutils/__init__.py | 4 ++-- {dpx => src/diffpy}/confutils/config.py | 2 +- {dpx => src/diffpy}/confutils/configtraits.py | 4 ++-- {dpx => src/diffpy}/confutils/gitarchive.cfg | 0 {dpx => src/diffpy}/confutils/tools.py | 0 {dpx => src/diffpy}/confutils/version.py | 0 7 files changed, 5 insertions(+), 5 deletions(-) rename {dpx => src/diffpy}/__init__.py (100%) rename {dpx => src/diffpy}/confutils/__init__.py (87%) rename {dpx => src/diffpy}/confutils/config.py (99%) rename {dpx => src/diffpy}/confutils/configtraits.py (98%) rename {dpx => src/diffpy}/confutils/gitarchive.cfg (100%) rename {dpx => src/diffpy}/confutils/tools.py (100%) rename {dpx => src/diffpy}/confutils/version.py (100%) diff --git a/dpx/__init__.py b/src/diffpy/__init__.py similarity index 100% rename from dpx/__init__.py rename to src/diffpy/__init__.py diff --git a/dpx/confutils/__init__.py b/src/diffpy/confutils/__init__.py similarity index 87% rename from dpx/confutils/__init__.py rename to src/diffpy/confutils/__init__.py index 8e62eb1..7541012 100644 --- a/dpx/confutils/__init__.py +++ b/src/diffpy/confutils/__init__.py @@ -13,10 +13,10 @@ ############################################################################## # package version -from dpx.confutils.version import __version__ +from diffpy.confutils.version import __version__ # some convenience imports -from dpx.confutils.config import ConfigBase, ConfigBase +from diffpy.confutils.config import ConfigBase, ConfigBase # unit tests def test(): diff --git a/dpx/confutils/config.py b/src/diffpy/confutils/config.py similarity index 99% rename from dpx/confutils/config.py rename to src/diffpy/confutils/config.py index f784e7f..4d8d6e3 100644 --- a/dpx/confutils/config.py +++ b/src/diffpy/confutils/config.py @@ -33,7 +33,7 @@ except: from ordereddict import OrderedDict -from dpx.confutils.tools import _configPropertyRad, _configPropertyR, \ +from diffpy.confutils.tools import _configPropertyRad, _configPropertyR, \ _configPropertyRW, str2bool, opt2Str, str2Opt, StrConv, FakeConfigFile class ConfigBase(object): diff --git a/dpx/confutils/configtraits.py b/src/diffpy/confutils/configtraits.py similarity index 98% rename from dpx/confutils/configtraits.py rename to src/diffpy/confutils/configtraits.py index d80d04c..f969e2a 100644 --- a/dpx/confutils/configtraits.py +++ b/src/diffpy/confutils/configtraits.py @@ -34,9 +34,9 @@ Event, CFloat, CInt, on_trait_change from traitsui.api import Item, Group, View -from dpx.confutils.tools import _configPropertyRad, _configPropertyR, _configPropertyRW, \ +from diffpy.confutils.tools import _configPropertyRad, _configPropertyR, _configPropertyRW, \ str2bool, opt2Str, str2Opt, StrConv -from dpx.confutils.config import ConfigBase +from diffpy.confutils.config import ConfigBase class ConfigBaseTraits(HasTraits, ConfigBase): ''' diff --git a/dpx/confutils/gitarchive.cfg b/src/diffpy/confutils/gitarchive.cfg similarity index 100% rename from dpx/confutils/gitarchive.cfg rename to src/diffpy/confutils/gitarchive.cfg diff --git a/dpx/confutils/tools.py b/src/diffpy/confutils/tools.py similarity index 100% rename from dpx/confutils/tools.py rename to src/diffpy/confutils/tools.py diff --git a/dpx/confutils/version.py b/src/diffpy/confutils/version.py similarity index 100% rename from dpx/confutils/version.py rename to src/diffpy/confutils/version.py From 274efae1bf7696418b80523faebac42b98618d1a Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sat, 16 Aug 2025 01:35:11 -0400 Subject: [PATCH 04/37] chore: rename confutils to srxconfutils --- src/diffpy/{confutils => srxconfutils}/__init__.py | 4 ++-- src/diffpy/{confutils => srxconfutils}/config.py | 2 +- src/diffpy/{confutils => srxconfutils}/configtraits.py | 4 ++-- src/diffpy/{confutils => srxconfutils}/gitarchive.cfg | 0 src/diffpy/{confutils => srxconfutils}/tools.py | 0 src/diffpy/{confutils => srxconfutils}/version.py | 0 6 files changed, 5 insertions(+), 5 deletions(-) rename src/diffpy/{confutils => srxconfutils}/__init__.py (87%) rename src/diffpy/{confutils => srxconfutils}/config.py (99%) rename src/diffpy/{confutils => srxconfutils}/configtraits.py (98%) rename src/diffpy/{confutils => srxconfutils}/gitarchive.cfg (100%) rename src/diffpy/{confutils => srxconfutils}/tools.py (100%) rename src/diffpy/{confutils => srxconfutils}/version.py (100%) diff --git a/src/diffpy/confutils/__init__.py b/src/diffpy/srxconfutils/__init__.py similarity index 87% rename from src/diffpy/confutils/__init__.py rename to src/diffpy/srxconfutils/__init__.py index 7541012..7b91ed3 100644 --- a/src/diffpy/confutils/__init__.py +++ b/src/diffpy/srxconfutils/__init__.py @@ -13,10 +13,10 @@ ############################################################################## # package version -from diffpy.confutils.version import __version__ +from diffpy.srxconfutils.version import __version__ # some convenience imports -from diffpy.confutils.config import ConfigBase, ConfigBase +from diffpy.srxconfutils.config import ConfigBase, ConfigBase # unit tests def test(): diff --git a/src/diffpy/confutils/config.py b/src/diffpy/srxconfutils/config.py similarity index 99% rename from src/diffpy/confutils/config.py rename to src/diffpy/srxconfutils/config.py index 4d8d6e3..c508365 100644 --- a/src/diffpy/confutils/config.py +++ b/src/diffpy/srxconfutils/config.py @@ -33,7 +33,7 @@ except: from ordereddict import OrderedDict -from diffpy.confutils.tools import _configPropertyRad, _configPropertyR, \ +from diffpy.srxconfutils.tools import _configPropertyRad, _configPropertyR, \ _configPropertyRW, str2bool, opt2Str, str2Opt, StrConv, FakeConfigFile class ConfigBase(object): diff --git a/src/diffpy/confutils/configtraits.py b/src/diffpy/srxconfutils/configtraits.py similarity index 98% rename from src/diffpy/confutils/configtraits.py rename to src/diffpy/srxconfutils/configtraits.py index f969e2a..502f63e 100644 --- a/src/diffpy/confutils/configtraits.py +++ b/src/diffpy/srxconfutils/configtraits.py @@ -34,9 +34,9 @@ Event, CFloat, CInt, on_trait_change from traitsui.api import Item, Group, View -from diffpy.confutils.tools import _configPropertyRad, _configPropertyR, _configPropertyRW, \ +from diffpy.srxconfutils.tools import _configPropertyRad, _configPropertyR, _configPropertyRW, \ str2bool, opt2Str, str2Opt, StrConv -from diffpy.confutils.config import ConfigBase +from diffpy.srxconfutils.config import ConfigBase class ConfigBaseTraits(HasTraits, ConfigBase): ''' diff --git a/src/diffpy/confutils/gitarchive.cfg b/src/diffpy/srxconfutils/gitarchive.cfg similarity index 100% rename from src/diffpy/confutils/gitarchive.cfg rename to src/diffpy/srxconfutils/gitarchive.cfg diff --git a/src/diffpy/confutils/tools.py b/src/diffpy/srxconfutils/tools.py similarity index 100% rename from src/diffpy/confutils/tools.py rename to src/diffpy/srxconfutils/tools.py diff --git a/src/diffpy/confutils/version.py b/src/diffpy/srxconfutils/version.py similarity index 100% rename from src/diffpy/confutils/version.py rename to src/diffpy/srxconfutils/version.py From 727d2c03b517eff219e100790f92d94401b775a6 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sat, 16 Aug 2025 01:41:17 -0400 Subject: [PATCH 05/37] chore: change dpx to src in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 94aa589..918ff52 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -43,7 +43,7 @@ dev_template = "{tag}" dirty_template = "{tag}" [tool.setuptools.packages.find] -where = ["dpx"] # list of folders that contain the packages (["."] by default) +where = ["src"] # list of folders that contain the packages (["."] by default) include = ["*"] # package names should match these glob patterns (["*"] by default) exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) From 1a987d8a5db47031ed92a047c15f7a9865514c20 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sun, 17 Aug 2025 17:15:13 -0400 Subject: [PATCH 06/37] fix: remove the use of the deprecated has_key function for dictionaries --- src/diffpy/srxconfutils/config.py | 24 ++++++++++++------------ src/diffpy/srxconfutils/configtraits.py | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/diffpy/srxconfutils/config.py b/src/diffpy/srxconfutils/config.py index c508365..3b672de 100644 --- a/src/diffpy/srxconfutils/config.py +++ b/src/diffpy/srxconfutils/config.py @@ -225,7 +225,7 @@ def _findConfigFile(self, filename=None, args=None, **kwargs): if ('--configfile' in args) or ('-c' in args): obj = self.args.parse_args(args) rv = obj.configfile - if kwargs.has_key('configfile'): + if 'configfile' in kwargs: rv = kwargs['configfile'] return rv @@ -314,7 +314,7 @@ class method, return the type of option :return: string, type of the option ''' optdata = cls._optdata[optname] - if optdata.has_key('t'): + if 't' in optdata: opttype = optdata['t'] else: value = optdata['d'] @@ -394,7 +394,7 @@ def _addOptC(cls, optname): cls._addOptSelfC(optname, optdata) # add to cls.config - secname = optdata['sec'] if optdata.has_key('sec') else 'Others' + 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']) @@ -404,14 +404,14 @@ def _addOptC(cls, optname): # transform optdata to a dict that can pass to add_argument method pargs = dict() for key in optdata.keys(): - if cls._optdatanamedict.has_key(key): + if key in cls._optdatanamedict: pargs[cls._optdatanamedict[key]] = optdata[key] pargs['default'] = argparse.SUPPRESS pargs['type'] = StrConv(opttype) # add args - if optdata.has_key('f'): + if 'f' in optdata: cls.args.add_argument(optname, **pargs) - elif optdata.has_key('s'): + elif 's' in optdata: cls.args.add_argument('--' + optname, '-' + optdata['s'], **pargs) else: cls.args.add_argument('--' + optname, **pargs) @@ -443,7 +443,7 @@ def _copyConfigtoSelf(self, optnames=None): optnames += self.config.options(secname) for optname in optnames: - if self._optdata.has_key(optname): + if optname in self._optdata: secname = self._optdata[optname]['sec'] opttype = self._getTypeStr(optname) optvalue = self.config.get(secname, optname) @@ -465,7 +465,7 @@ def _copySelftoConfig(self, optnames=None): optnames += self.config.options(secname) for optname in optnames: - if self._optdata.has_key(optname): + if optname in self._optdata: secname = self._optdata[optname]['sec'] opttype = self._getTypeStr(optname) optvalue = getattr(self, optname) @@ -484,7 +484,7 @@ def parseArgs(self, pargs): obj = self.args.parse_args(pargs) changedargs = obj.__dict__.keys() for optname in changedargs: - if self._optdata.has_key(optname): + if optname in self._optdata: setattr(self, optname, getattr(obj, optname)) # update self if len(changedargs) > 0: @@ -500,7 +500,7 @@ def parseKwargs(self, **kwargs): if kwargs != {}: changedargs = [] for optname, optvalue in kwargs.iteritems(): - if self._optdata.has_key(optname): + if optname in self._optdata: setattr(self, optname, optvalue) changedargs.append(optname) # update self @@ -669,7 +669,7 @@ def resetDefault(self, optnames=None): if optnames == None: optnames = self._optdata.keys() for optname in optnames: - if self._optdata.has_key(optname): + if optname in self._optdata: setattr(self, optname, self._optdata[optname]['d']) self._updateSelf() return @@ -687,7 +687,7 @@ class to add options as class attributes!!! ''' cls._preInitConfigClass() - cls.config = ConfigParser.ConfigParser(dict_type=OrderedDict) + cls.config = ConfigParser(dict_type=OrderedDict) cls.args = argparse.ArgumentParser(description=cls._description, epilog=cls._epilog, formatter_class=argparse.RawDescriptionHelpFormatter) diff --git a/src/diffpy/srxconfutils/configtraits.py b/src/diffpy/srxconfutils/configtraits.py index 502f63e..0262dec 100644 --- a/src/diffpy/srxconfutils/configtraits.py +++ b/src/diffpy/srxconfutils/configtraits.py @@ -235,11 +235,11 @@ class method, assign options value to *self.option*, using metadata, vtype = cls._getTypeStrC(optname) ttype = optdata.get('tt', vtype) ttype = cls._traitstypedict[ttype] - kwargs = {'label':optdata['l'] if optdata.has_key('l') else optname, + kwargs = {'label':optdata['l'] if 'l' in optdata else optname, 'desc':optdata['h'], } args = [optdata['d']] - if optdata.has_key('c'): + if 'c' in optdata: ttype = Enum args = [optdata['c']] kwargs['value']=optdata['d'] From a1ce96eb06ec5b33752a94b65734a8f9623e586a Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sat, 23 Aug 2025 14:39:39 -0400 Subject: [PATCH 07/37] chore: update dict.iteritems() to dict.items() --- src/diffpy/srxconfutils/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/diffpy/srxconfutils/config.py b/src/diffpy/srxconfutils/config.py index 3b672de..d5742bf 100644 --- a/src/diffpy/srxconfutils/config.py +++ b/src/diffpy/srxconfutils/config.py @@ -499,7 +499,7 @@ def parseKwargs(self, **kwargs): ''' if kwargs != {}: changedargs = [] - for optname, optvalue in kwargs.iteritems(): + for optname, optvalue in kwargs.items(): if optname in self._optdata: setattr(self, optname, optvalue) changedargs.append(optname) From 1fb129d19456bef3cb96ca9853f22e760ce93f12 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Wed, 27 Aug 2025 16:22:46 -0400 Subject: [PATCH 08/37] fix: add __iter__ and __next__ methods, and convert map object to a list object --- src/diffpy/srxconfutils/tools.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/diffpy/srxconfutils/tools.py b/src/diffpy/srxconfutils/tools.py index 2ad874a..26fa5e2 100644 --- a/src/diffpy/srxconfutils/tools.py +++ b/src/diffpy/srxconfutils/tools.py @@ -110,7 +110,7 @@ def str2Opt(opttype, optvalue): conv = StrConv(opttype) if opttype.endswith('list'): temp = re.split('\s*,\s*', optvalue) - rv = map(conv, temp) if len(temp) > 0 else [] + rv = list(map(conv, temp)) if len(temp) > 0 else [] else: rv = conv(optvalue) return rv @@ -146,6 +146,15 @@ def close(self): ''' self.fp.close() return + + def __iter__(self): + return self + + def __next__(self): + line = self.readline() + if line == '': + raise StopIteration + return line def checkCRC32(filename): ''' From d2d5e7727f5d582fe9db025451eb2821f7bd7318 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Thu, 28 Aug 2025 17:26:59 -0400 Subject: [PATCH 09/37] skpkg: move over precommit files and create pre-commit auto fixes --- .codespell/ignore_lines.txt | 2 + .codespell/ignore_words.txt | 8 + .flake8 | 13 + .isort.cfg | 5 + .pre-commit-config.yaml | 66 +++ AUTHORS.txt | 4 +- README.rst | 10 +- requirements/pip.txt | 2 +- src/diffpy/__init__.py | 7 +- src/diffpy/srxconfutils/__init__.py | 11 +- src/diffpy/srxconfutils/config.py | 724 +++++++++++++----------- src/diffpy/srxconfutils/configtraits.py | 406 ++++++++----- src/diffpy/srxconfutils/gitarchive.cfg | 2 +- src/diffpy/srxconfutils/tools.py | 178 +++--- src/diffpy/srxconfutils/version.py | 12 +- 15 files changed, 865 insertions(+), 585 deletions(-) create mode 100644 .codespell/ignore_lines.txt create mode 100644 .codespell/ignore_words.txt create mode 100644 .flake8 create mode 100644 .isort.cfg create mode 100644 .pre-commit-config.yaml diff --git a/.codespell/ignore_lines.txt b/.codespell/ignore_lines.txt new file mode 100644 index 0000000..07fa7c8 --- /dev/null +++ b/.codespell/ignore_lines.txt @@ -0,0 +1,2 @@ +;; Please include filenames and explanations for each ignored line. +;; See https://docs.openverse.org/meta/codespell.html for docs. diff --git a/.codespell/ignore_words.txt b/.codespell/ignore_words.txt new file mode 100644 index 0000000..04b4fcf --- /dev/null +++ b/.codespell/ignore_words.txt @@ -0,0 +1,8 @@ +;; Please include explanations for each ignored word (lowercase). +;; See https://docs.openverse.org/meta/codespell.html for docs. + +;; abbreviation for "materials" often used in a journal title +mater + +;; Frobenius norm used in np.linalg.norm +fro diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..88077af --- /dev/null +++ b/.flake8 @@ -0,0 +1,13 @@ +# As of now, flake8 does not natively support configuration via pyproject.toml +# https://github.com/microsoft/vscode-flake8/issues/135 +[flake8] +exclude = + .git, + __pycache__, + build, + dist, + docs/source/conf.py +max-line-length = 79 +# Ignore some style 'errors' produced while formatting by 'black' +# https://black.readthedocs.io/en/stable/guides/using_black_with_other_tools.html#labels-why-pycodestyle-warnings +extend-ignore = E203 diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 0000000..86f162b --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,5 @@ +[settings] +# Keep import statement below line_length character limit +line_length = 79 +multi_line_output = 3 +include_trailing_comma = True diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0e4a84d --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,66 @@ +default_language_version: + python: python3 +ci: + autofix_commit_msg: | + [pre-commit.ci] auto fixes from pre-commit hooks + autofix_prs: true + autoupdate_branch: "pre-commit-autoupdate" + autoupdate_commit_msg: "[pre-commit.ci] pre-commit autoupdate" + autoupdate_schedule: monthly + skip: [no-commit-to-branch] + submodules: false +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-case-conflict + - id: check-merge-conflict + - id: check-toml + - id: check-added-large-files + - repo: https://github.com/psf/black + rev: 24.4.2 + hooks: + - id: black + - repo: https://github.com/pycqa/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + - repo: https://github.com/pycqa/isort + rev: 5.13.2 + hooks: + - id: isort + args: ["--profile", "black"] + - repo: https://github.com/kynan/nbstripout + rev: 0.7.1 + hooks: + - id: nbstripout + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.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 + 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 + hooks: + - id: prettier + additional_dependencies: + - "prettier@^3.2.4" + # docformatter - PEP 257 compliant docstring formatter + - repo: https://github.com/s-weigand/docformatter + rev: 5757c5190d95e5449f102ace83df92e7d3b06c6c + hooks: + - id: docformatter + additional_dependencies: [tomli] + args: [--in-place, --config, ./pyproject.toml] diff --git a/AUTHORS.txt b/AUTHORS.txt index 1ea282b..cddf273 100644 --- a/AUTHORS.txt +++ b/AUTHORS.txt @@ -2,6 +2,6 @@ This code is developed by: Xiaohao Yang -This code was developed as part of the xPDFsuite project to create software -and tools for general researchers to use PDF in their work. For more +This code was developed as part of the xPDFsuite project to create software +and tools for general researchers to use PDF in their work. For more information on the DiffPy project email sb2896@columbia.edu diff --git a/README.rst b/README.rst index 720412f..e78479f 100644 --- a/README.rst +++ b/README.rst @@ -17,13 +17,13 @@ INSTALLATION ------------------------------------------------------------------------ We are going to release conda package for all platform. For general user -please use the installation file and install software. For developor, +please use the installation file and install software. For developor, you can install dpx.confutils using python setup.py install - -Note: the dependency is not specified in the setup.py. You need to install -them yourself. You can use Anaconda or other python enviroment. + +Note: the dependency is not specified in the setup.py. You need to install +them yourself. You can use Anaconda or other python enviroment. CONTACTS @@ -33,4 +33,4 @@ For more information on diffpy.Structure please visit the project web-page http://www.diffpy.org/ -or email Prof. Simon Billinge at sb2896@columbia.edu. \ No newline at end of file +or email Prof. Simon Billinge at sb2896@columbia.edu. diff --git a/requirements/pip.txt b/requirements/pip.txt index f8d7a2e..59ebc00 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1,4 +1,4 @@ numpy configparser traits -traitsui \ No newline at end of file +traitsui diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 1988b11..815245d 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -11,11 +11,8 @@ # See LICENSENOTICE.txt for license information. # ############################################################################## +"""Blank namespace package.""" -""" -Blank namespace package. -""" - -__import__('pkg_resources').declare_namespace(__name__) +__import__("pkg_resources").declare_namespace(__name__) # End of file diff --git a/src/diffpy/srxconfutils/__init__.py b/src/diffpy/srxconfutils/__init__.py index 7b91ed3..29aa678 100644 --- a/src/diffpy/srxconfutils/__init__.py +++ b/src/diffpy/srxconfutils/__init__.py @@ -12,18 +12,21 @@ # ############################################################################## +# some convenience imports +from diffpy.srxconfutils.config import ConfigBase + # package version from diffpy.srxconfutils.version import __version__ -# some convenience imports -from diffpy.srxconfutils.config import ConfigBase, ConfigBase # unit tests def test(): - '''Execute all unit tests for the diffpy.pdfgetx package. + """Execute all unit tests for the diffpy.pdfgetx package. + Return a unittest TestResult object. - ''' + """ from dpx.confutils.tests import test + return test() diff --git a/src/diffpy/srxconfutils/config.py b/src/diffpy/srxconfutils/config.py index d5742bf..77a0afe 100644 --- a/src/diffpy/srxconfutils/config.py +++ b/src/diffpy/srxconfutils/config.py @@ -11,48 +11,55 @@ # See LICENSENOTICE.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. -''' -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 -''' +""" -from configparser import ConfigParser -import re +import argparse import os +import re import sys +from configparser import ConfigParser from functools import partial -import argparse + try: from collections import OrderedDict except: from ordereddict import OrderedDict -from diffpy.srxconfutils.tools import _configPropertyRad, _configPropertyR, \ - _configPropertyRW, str2bool, opt2Str, str2Opt, StrConv, FakeConfigFile +from diffpy.srxconfutils.tools import ( + FakeConfigFile, + StrConv, + _configPropertyR, + _configPropertyRad, + _configPropertyRW, + opt2Str, + str2bool, + str2Opt, +) + class ConfigBase(object): - ''' - _optdatalist_default, _optdatalist are metadata used to - initialize the options, see below for examples - + """_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 + 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 @@ -61,25 +68,23 @@ class ConfigBase(object): 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 - ''' + _description = """Description of configurations + """ # Text to display after the argument help - _epilog = \ - ''' - ''' + _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 @@ -91,100 +96,170 @@ class ConfigBase(object): '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'} + """ + _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':'', }], - ] + [ + "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':'filp 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 \ - (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], }], - ] + [ + "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": "filp 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 \ + (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' - } - + _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 - + """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) @@ -195,11 +270,11 @@ def __init__(self, filename=None, args=None, **kwargs): # 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 - ''' + """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']) @@ -208,62 +283,58 @@ def _preInit(self, **kwargs): ########################################################################### 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. - + """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 - ''' + :return: name of config file if found, otherwise None + """ rv = None - if (filename != None): + if filename != None: rv = filename - if (args != None): - if ('--configfile' in args) or ('-c' in args): + if args != None: + if ("--configfile" in args) or ("-c" in args): obj = self.args.parse_args(args) rv = obj.configfile - if 'configfile' in kwargs: - rv = kwargs['configfile'] + if "configfile" in kwargs: + 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. - + """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 - ''' + + return: name of config file if found, otherwise None + """ rv = self._findConfigFile(filename, args, **kwargs) if rv == None: - for dconf in self._defaultdata['configfile']: - if (os.path.exists(dconf))and(rv == None): + for dconf in self._defaultdata["configfile"]: + if (os.path.exists(dconf)) and (rv == 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 - + """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 @@ -274,90 +345,78 @@ def _updateSelf(self, optnames=None, **kwargs): # 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) - ''' + """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) - ''' + """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 - + """Return the type of option. + :param optname: str, name of option - - :return: string, type of the 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 - + """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 - ''' + :return: string, type of the option + """ optdata = cls._optdata[optname] - if 't' in optdata: - opttype = optdata['t'] + if "t" in optdata: + opttype = optdata["t"] else: - value = optdata['d'] + value = optdata["d"] if isinstance(value, str): - opttype = 'str' + opttype = "str" elif isinstance(value, bool): - opttype = 'bool' + opttype = "bool" elif isinstance(value, float): - opttype = 'float' + opttype = "float" elif isinstance(value, int): - opttype = 'int' + opttype = "int" elif isinstance(value, list): if len(value) == 0: - opttype = 'strlist' + opttype = "strlist" elif isinstance(value[0], str): - opttype = 'strlist' + opttype = "strlist" elif isinstance(value[0], bool): - opttype = 'boollist' + opttype = "boollist" elif isinstance(value[0], float): - opttype = 'floatlist' + opttype = "floatlist" elif isinstance(value[0], int): - opttype = 'intlist' + 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 - ''' + """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 - ''' + """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] + 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(): @@ -366,75 +425,80 @@ class method, detect sections present in self._optdata and add them to self.conf return def _addOpt(self, optname): - ''' - add options to self.config and self.args and self.*option*, - this will read metadata from self._optdatalist - + """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 - + """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() + 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' + 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']) + 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': + 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) + pargs["default"] = argparse.SUPPRESS + pargs["type"] = StrConv(opttype) # add args - if 'f' in optdata: + if "f" in optdata: cls.args.add_argument(optname, **pargs) - elif 's' in optdata: - cls.args.add_argument('--' + optname, '-' + optdata['s'], **pargs) + elif "s" in optdata: + cls.args.add_argument( + "--" + optname, "-" + optdata["s"], **pargs + ) else: - cls.args.add_argument('--' + optname, **pargs) + cls.args.add_argument("--" + optname, **pargs) return @classmethod def _addOptSelfC(cls, optname, optdata): - ''' - class method, assign options value to *self.option*, using metadata - + """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']) + :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 - ''' + """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 != None: optnames = optnames if isinstance(optnames, list) else [optnames] else: @@ -444,19 +508,19 @@ def _copyConfigtoSelf(self, optnames=None): for optname in optnames: if optname in self._optdata: - secname = self._optdata[optname]['sec'] + 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 - ''' + """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 != None: optnames = optnames if isinstance(optnames, list) else [optnames] else: @@ -466,7 +530,7 @@ def _copySelftoConfig(self, optnames=None): for optname in optnames: if optname in self._optdata: - secname = self._optdata[optname]['sec'] + secname = self._optdata[optname]["sec"] opttype = self._getTypeStr(optname) optvalue = getattr(self, optname) self.config.set(secname, optname, opt2Str(opttype, optvalue)) @@ -475,12 +539,12 @@ def _copySelftoConfig(self, optnames=None): ########################################################################### 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 comming from sys.argv - ''' + """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 + comming from sys.argv + """ obj = self.args.parse_args(pargs) changedargs = obj.__dict__.keys() for optname in changedargs: @@ -492,11 +556,10 @@ def parseArgs(self, pargs): return obj def parseKwargs(self, **kwargs): - ''' - update self.*option* values according to the kwargs - + """Update self.*option* values according to the kwargs. + :param kwargs: dict, keywords=value - ''' + """ if kwargs != {}: changedargs = [] for optname, optvalue in kwargs.items(): @@ -508,11 +571,10 @@ def parseKwargs(self, **kwargs): return def parseConfigFile(self, filename): - ''' - read a config file and update the self.*option* - + """Read a config file and update the self.*option* + :param filename: str, file name of config file (include path) - ''' + """ if filename != None: filename = os.path.abspath(filename) if os.path.exists(filename): @@ -526,21 +588,21 @@ def parseConfigFile(self, filename): return def updateConfig(self, filename=None, args=None, **kwargs): - ''' - update config according to config file, args(from sys.argv) or **kwargs - + """Update config according to config file, args(from sys.argv) + or **kwargs. + 1. call self._preUpdateConfig() - 2. process file/args/kwargs passed to this method, + 2. process file/args/kwargs passed to this method, 3. read a configfile 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 args: list of str, args passed from cmd, :param kwargs: dict, optional kwargs - + :return: True if anything updated, False if nothing updated - ''' + """ # call self._preUpdateConfig self._preUpdateConfig(**kwargs) @@ -552,7 +614,11 @@ def updateConfig(self, filename=None, args=None, **kwargs): if kwargs != {}: rv = self.parseKwargs(**kwargs) - if (filename == None)and((args == None)or(args == []))and(kwargs == {}): + if ( + (filename == None) + and ((args == None) or (args == [])) + and (kwargs == {}) + ): rv = self._updateSelf() # call self._callbackUpdateConfig @@ -563,87 +629,107 @@ def updateConfig(self, filename=None, args=None, **kwargs): return rv def _preUpdateConfig(self, **kwargs): - ''' - Method called before parsing args or kwargs or config file, in self.updateConfig - ''' + """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 - ''' + """Method called after parsing args or kwargs or config file, in + self.updateConfig.""" return ########################################################################### def _createConfigFile(self): - ''' - write output config file if specfied in configuration - the filename is specified by self.createconfig - ''' - if (self.createconfig != '')and(self.createconfig != None): - self.writeConfig(self.createconfig, 'short') - self.createconfig = '' - if (self.createconfigfull != '')and(self.createconfigfull != None): - self.writeConfig(self.createconfigfull, 'full') - self.createconfigfull = '' + """Write output config file if specfied in configuration the + filename is specified by self.createconfig.""" + if (self.createconfig != "") and (self.createconfig != None): + self.writeConfig(self.createconfig, "short") + self.createconfig = "" + if (self.createconfigfull != "") and (self.createconfigfull != None): + self.writeConfig(self.createconfigfull, "full") + self.createconfigfull = "" return - def writeConfig(self, filename, mode='short', changeconfigfile=True): - ''' - write config to file. the file is compatiable with python package ConfigParser - + def writeConfig(self, filename, mode="short", changeconfigfile=True): + """Write config to file. the file is compatiable 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, + :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 wirte 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' + 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' + 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(): + 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'))) + tlines.append( + "%s = %s" % (key, str(value).replace("\n", "\n\t")) + ) if len(tlines) > 0: lines.append("[%s]" % section) lines.extend(tlines) - lines.append('') + lines.append("") rv = "\n".join(lines) + "\n" - fp = open(filename, 'w') + 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, + 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 writen to a text file - ''' + :return: string, lines with line break that can be directly + writen to a text file + """ lines = [] - title = '# %s #' % (self._defaultdata['headertitle'] if title == None else title) + title = "# %s #" % ( + self._defaultdata["headertitle"] if title == None else title + ) lines.append(title) # func decide if wirte 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' + 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' + mcond = ( + lambda optname: self._optdata.get( + optname, {"header": "n"} + ).get("header", "a") + != "n" + ) for secname in self._configlist.keys(): tlines = [] @@ -651,46 +737,51 @@ def getHeader(self, title=None, mode='full'): if mcond(optname): value = getattr(self, optname) ttype = self._getTypeStr(optname) - strvalue = ', '.join(map(str, value)) if ttype.endswith('list') else str(value) + 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('') + 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 - ''' + """Reset all values to their default value. + + :param optnames: list of str, name of options to reset, None for + all options + """ if optnames == None: optnames = self._optdata.keys() for optname in optnames: if optname in self._optdata: - setattr(self, optname, self._optdata[optname]['d']) + setattr(self, optname, self._optdata[optname]["d"]) self._updateSelf() return ########################################################################### - #IMPORTANT call this method if you want to add options as class attributes!!! + # 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!!! - ''' + """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.args = argparse.ArgumentParser( + description=cls._description, + epilog=cls._epilog, + formatter_class=argparse.RawDescriptionHelpFormatter, + ) cls._configlist = OrderedDict({}) cls._optdatalist = cls._optdatalist_default + cls._optdatalist @@ -705,28 +796,27 @@ class to add options as class attributes!!! @classmethod def _postInitConfigClass(cls): - ''' - additional processes called after initConfigClass - + """Additional processes called after initConfigClass. + overload it - ''' + """ pass @classmethod def _preInitConfigClass(cls): - ''' - additional processes called before initConfigClass - + """Additional processes called before initConfigClass. + overload it - ''' + """ pass -#VERY IMPORTANT!!! + +# VERY IMPORTANT!!! # add options to class # initConfigClass(ConfigBase) # ConfigBase.initConfigClass() -if __name__ == '__main__': +if __name__ == "__main__": test = ConfigBase() test.updateConfig() diff --git a/src/diffpy/srxconfutils/configtraits.py b/src/diffpy/srxconfutils/configtraits.py index 0262dec..d71b84f 100644 --- a/src/diffpy/srxconfutils/configtraits.py +++ b/src/diffpy/srxconfutils/configtraits.py @@ -11,50 +11,73 @@ # See LICENSENOTICE.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. +"""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. This one is similar to ConfigBase but use Traits, so every option (self.*option* is a trait) Note: for python 2.6, argparse and orderedDict is required, install them with easy_install -''' +""" -from configparser import ConfigParser -import re +import argparse import os +import re import sys +from configparser import ConfigParser from functools import partial -import argparse -from traits.api import Directory, String, List, Enum, Bool, File, Float, Int, \ - HasTraits, Property, Range, cached_property, Str, Instance, Array,\ - Event, CFloat, CInt, on_trait_change -from traitsui.api import Item, Group, View +from traits.api import ( + Array, + Bool, + CFloat, + CInt, + Directory, + Enum, + Event, + File, + Float, + HasTraits, + Instance, + Int, + List, + Property, + Range, + Str, + String, + cached_property, + on_trait_change, +) +from traitsui.api import Group, Item, View -from diffpy.srxconfutils.tools import _configPropertyRad, _configPropertyR, _configPropertyRW, \ - str2bool, opt2Str, str2Opt, StrConv from diffpy.srxconfutils.config import ConfigBase +from diffpy.srxconfutils.tools import ( + StrConv, + _configPropertyR, + _configPropertyRad, + _configPropertyRW, + opt2Str, + str2bool, + str2Opt, +) + class ConfigBaseTraits(HasTraits, ConfigBase): - ''' - _optdatalist_default, _optdatalist are metadata used to - initialize the options, see below for examples - + """_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 + 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 @@ -63,25 +86,23 @@ class ConfigBaseTraits(HasTraits, ConfigBase): 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 - ''' + _description = """Description of configurations + """ # Text to display after the argument help - _epilog = \ - ''' - ''' - - ''' + _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 @@ -93,108 +114,179 @@ class ConfigBaseTraits(HasTraits, ConfigBase): 'r': required 'de': dest 'co': const - + additional options for traits: 'tt': traits type 'l': traits label - ''' - _optdatanamedict = {'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", + } _traitstypedict = { - 'str': String, - 'int': CInt, - 'float': CFloat, - 'bool': Bool, - 'file': File, - 'directory': Directory, - 'strlist':List, - 'intlist':List, - 'floatlist':List, - 'boollist':List, - 'array':Array, - } - - #examples, overload it + "str": String, + "int": CInt, + "float": CFloat, + "bool": Bool, + "file": File, + "directory": Directory, + "strlist": List, + "intlist": List, + "floatlist": List, + "boollist": List, + "array": Array, + } + + # examples, overload it _optdatalist_default = [ - ['configfile',{'sec':'Control', 'config':'f', 'header':'n', - 'l':'Config File', - 'tt':'file', - '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 + [ + "configfile", + { + "sec": "Control", + "config": "f", + "header": "n", + "l": "Config File", + "tt": "file", + "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', - 'tt':'directory', - 'l':'Tif directory', - 's':'tifdir', - 'h':'directory of raw tif files', - 'd':'currentdir',}], - ['integrationspace',{'sec':'Experiment', - 'l':'Integration space', - 'h':'integration space, could be twotheta or qspace', - 'd':'twotheta', - 'c':['twotheta','qspace'],}], - ['wavelength',{'sec':'Experiment', - 'l':'Wavelength', - 'h':'wavelength of x-ray, in A', - 'd':0.1000,}], - ['rotationd',{'sec':'Experiment', - 'l':'Tilt Rotation', - 's':'rot', - 'h':'rotation angle of tilt plane, in degree', - 'd':0.0,}], - ['includepattern',{'sec':'Beamline','header':'n','config':'f', - 'l':'Include', - 's':'ipattern', - 'h':'file name pattern for included files', - 'n':'*', - 'd':['*.tif'],}], - ['excludepattern',{'sec':'Beamline','header':'n','config':'f', - 'l':'Exclude', - 's':'epattern', - 'h':'file name pattern for excluded files', - 'n':'*', - 'd':['*.dark.tif', '*.raw.tif'],}], - ['fliphorizontal',{'sec':'Beamline','header':'n','config':'f', - 'l':'Filp horizontally', - 'h':'filp the image horizontally', - 'n':'?', - 'co':True, - 'd':False,}], - ['maskedges',{'sec':'Others','config':'f', - 'tt':'array', - 'l':'Mask edges', - 'h':'mask the edge pixels, first four means the number of pixels masked in each edge \ - (left, right, top, bottom), the last one is the radius of a region masked around the corner', - 'n':5, - 'd':[10,10,10,10,100],}], - ] - - #default config file path and name - _defaultdata = {'configfile': ['config.cfg'], - 'headertitle': 'Configuration information' - } - + [ + "tifdirectory", + { + "sec": "Experiment", + "header": "n", + "tt": "directory", + "l": "Tif directory", + "s": "tifdir", + "h": "directory of raw tif files", + "d": "currentdir", + }, + ], + [ + "integrationspace", + { + "sec": "Experiment", + "l": "Integration space", + "h": "integration space, could be twotheta or qspace", + "d": "twotheta", + "c": ["twotheta", "qspace"], + }, + ], + [ + "wavelength", + { + "sec": "Experiment", + "l": "Wavelength", + "h": "wavelength of x-ray, in A", + "d": 0.1000, + }, + ], + [ + "rotationd", + { + "sec": "Experiment", + "l": "Tilt Rotation", + "s": "rot", + "h": "rotation angle of tilt plane, in degree", + "d": 0.0, + }, + ], + [ + "includepattern", + { + "sec": "Beamline", + "header": "n", + "config": "f", + "l": "Include", + "s": "ipattern", + "h": "file name pattern for included files", + "n": "*", + "d": ["*.tif"], + }, + ], + [ + "excludepattern", + { + "sec": "Beamline", + "header": "n", + "config": "f", + "l": "Exclude", + "s": "epattern", + "h": "file name pattern for excluded files", + "n": "*", + "d": ["*.dark.tif", "*.raw.tif"], + }, + ], + [ + "fliphorizontal", + { + "sec": "Beamline", + "header": "n", + "config": "f", + "l": "Filp horizontally", + "h": "filp the image horizontally", + "n": "?", + "co": True, + "d": False, + }, + ], + [ + "maskedges", + { + "sec": "Others", + "config": "f", + "tt": "array", + "l": "Mask edges", + "h": "mask the edge pixels, first four means the number of pixels masked in each edge \ + (left, right, top, bottom), the last one is the radius of a region masked around the corner", + "n": 5, + "d": [10, 10, 10, 10, 100], + }, + ], + ] + + # default config file path and name + _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 - + """Init the class and update the values of options if specified + in filename/args/kwargs. + it will: 1. init class using HasTraits 2. call self._preInit method @@ -203,56 +295,56 @@ def __init__(self, filename=None, args=None, **kwargs): 4. update the options value using filename/args/kwargs file > args > kwargs 5. call self._postInitTraits() - + :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 - ''' + """ HasTraits.__init__(self) ConfigBase.__init__(self, filename, args, **kwargs) - + self._postInitTraits() return - + def _postInitTraits(self): - ''' - additional init process called after traits init - ''' + """Additional init process called after traits init.""" return - + @classmethod def _addOptSelfC(cls, optname, optdata): - ''' - class method, assign options value to *self.option*, using metadata, - this one will create traits objects for each option - + """Class method, assign options value to *self.option*, using + metadata, this one will create traits objects for each option. + :param optname: string, name of the option - :param optdata: dict, metadata of the options, get it from self._optdatalist - ''' - #value type + :param optdata: dict, metadata of the options, get it from + self._optdatalist + """ + # value type vtype = cls._getTypeStrC(optname) - ttype = optdata.get('tt', vtype) + ttype = optdata.get("tt", vtype) ttype = cls._traitstypedict[ttype] - kwargs = {'label':optdata['l'] if 'l' in optdata else optname, - 'desc':optdata['h'], - } - args = [optdata['d']] - if 'c' in optdata: + kwargs = { + "label": optdata["l"] if "l" in optdata else optname, + "desc": optdata["h"], + } + args = [optdata["d"]] + if "c" in optdata: ttype = Enum - args = [optdata['c']] - kwargs['value']=optdata['d'] + args = [optdata["c"]] + kwargs["value"] = optdata["d"] if ttype == Array: args = [] - kwargs['value']=optdata['d'] + kwargs["value"] = optdata["d"] obj = ttype(*args, **kwargs) cls.add_class_trait(optname, obj) return -#ConfigBaseTraits.initConfigClass() -if __name__=='__main__': - test = ConfigBaseTraits(filename='temp.cfg') +# ConfigBaseTraits.initConfigClass() + +if __name__ == "__main__": + test = ConfigBaseTraits(filename="temp.cfg") test.updateConfig() test.configure_traits() diff --git a/src/diffpy/srxconfutils/gitarchive.cfg b/src/diffpy/srxconfutils/gitarchive.cfg index 2f8fb70..43b349f 100644 --- a/src/diffpy/srxconfutils/gitarchive.cfg +++ b/src/diffpy/srxconfutils/gitarchive.cfg @@ -2,4 +2,4 @@ commit = $Format:%H$ date = $Format:%ai$ timestamp = $Format:%at$ -refnames = $Format:%D$ \ No newline at end of file +refnames = $Format:%D$ diff --git a/src/diffpy/srxconfutils/tools.py b/src/diffpy/srxconfutils/tools.py index 26fa5e2..b64baa0 100644 --- a/src/diffpy/srxconfutils/tools.py +++ b/src/diffpy/srxconfutils/tools.py @@ -12,115 +12,125 @@ # ############################################################################## -import numpy as np +import hashlib import re import time import zlib -import hashlib from pkgutil import iter_modules +import numpy as np + + def module_exists(module_name): return module_name in [tuple_[1] for tuple_ in iter_modules()] + def module_exists_lower(module_name): - return module_name.lower() in [tuple_[1].lower() for tuple_ in iter_modules()] + return module_name.lower() in [ + tuple_[1].lower() for tuple_ in iter_modules() + ] + 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)) + """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. - + """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') + """ + 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. - + """Create a property that forwards self.name to self.config.name. + read and write - ''' - rv = property(fget=lambda self: getattr(self.config, nm), - fset=lambda self, value: setattr(self.config, nm, value), - fdel=lambda self: delattr(self, nm), - doc='attribute forwarded to self.config, read/write') + """ + rv = property( + fget=lambda self: getattr(self.config, nm), + fset=lambda self, value: setattr(self.config, nm, value), + fdel=lambda self: delattr(self, nm), + doc="attribute forwarded to self.config, read/write", + ) return rv + def str2bool(v): - ''' - turn string to bool - ''' + """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 truned into "value1, value2, value3..." - - :param opttype: string, type of opitons, for example 'str' or 'intlist' + """Turn the value of one option to string, according to the option + type list of values are truned into "value1, value2, value3...". + + :param opttype: string, type of opitons, 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)) + if opttype.endswith("list"): + rv = ", ".join(map(str, optvalue)) else: rv = str(optvalue) return rv + def StrConv(opttype): - ''' - get the type (or converter function) according to the opttype - - the function doesn't take list - ''' - if opttype.startswith('str'): + """Get the type (or converter function) according to the opttype. + + the function doesn't take list + """ + if opttype.startswith("str"): conv = str - elif opttype.startswith('int'): + elif opttype.startswith("int"): conv = int - elif opttype.startswith('float'): + elif opttype.startswith("float"): conv = float - elif opttype.startswith('bool'): + 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 opitons, for example 'str' or 'intlist' + """Convert the string to value of one option, according to the + option type. + + :param opttype: string, type of opitons, 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('\s*,\s*', optvalue) + if opttype.endswith("list"): + temp = re.split("\s*,\s*", optvalue) rv = list(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='###'): + """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 @@ -129,45 +139,40 @@ def __init__(self, configfile, endline='###'): return def readline(self): - ''' - readline function - ''' + """Readline function.""" line = self.fp.readline() if line.startswith(self.endline) or self.ended: - rv = '' + rv = "" self.ended = True else: rv = line return rv def close(self): - ''' - close the file - ''' + """Close the file.""" self.fp.close() return - + def __iter__(self): return self - + def __next__(self): line = self.readline() - if line == '': + if line == "": raise StopIteration return line + def checkCRC32(filename): - ''' - calculate the crc32 value of file - + """Calculate the crc32 value of file. + :param filename: path to the file - :return: crc32 value of file - ''' + """ try: - fd = open(filename, 'rb') + fd = open(filename, "rb") except: - return 'Read error' + return "Read error" eachLine = fd.readline() prev = 0 while eachLine: @@ -176,18 +181,17 @@ def checkCRC32(filename): fd.close() return prev + def checkMD5(filename, blocksize=65536): - ''' - calculate the MD5 value of file - + """Calculate the MD5 value of file. + :param filename: path to the file - :return: md5 value of file - ''' + """ try: - fd = open(filename, 'rb') + fd = open(filename, "rb") except: - return 'Read error' + return "Read error" buf = fd.read(blocksize) md5 = hashlib.md5() while len(buf) > 0: @@ -196,14 +200,14 @@ def checkMD5(filename, blocksize=65536): 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. - + """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: diff --git a/src/diffpy/srxconfutils/version.py b/src/diffpy/srxconfutils/version.py index 4f60568..45736e1 100644 --- a/src/diffpy/srxconfutils/version.py +++ b/src/diffpy/srxconfutils/version.py @@ -11,17 +11,17 @@ # See LICENSENOTICE.txt for license information. # ############################################################################## - -"""Definition of __version__ and __date__ for this package. -""" +"""Definition of __version__ and __date__ for this package.""" # obtain version information from pkg_resources import get_distribution -_pkgname = __name__.rsplit('.', 1)[0] + +_pkgname = __name__.rsplit(".", 1)[0] __version__ = get_distribution(_pkgname).version # we assume that tag_date was used and __version__ ends in YYYYMMDD -__date__ = __version__[-8:-4] + '-' + \ - __version__[-4:-2] + '-' + __version__[-2:] +__date__ = ( + __version__[-8:-4] + "-" + __version__[-4:-2] + "-" + __version__[-2:] +) # End of file From 57e4561abcf9285a0bdd21400a194f4437fb4076 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sun, 31 Aug 2025 17:01:16 -0400 Subject: [PATCH 10/37] style: fix codespell errors --- README.rst | 2 +- src/diffpy/srxconfutils/config.py | 16 ++++++++-------- src/diffpy/srxconfutils/configtraits.py | 4 ++-- src/diffpy/srxconfutils/tools.py | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/README.rst b/README.rst index e78479f..b9418be 100644 --- a/README.rst +++ b/README.rst @@ -23,7 +23,7 @@ you can install dpx.confutils using python setup.py install Note: the dependency is not specified in the setup.py. You need to install -them yourself. You can use Anaconda or other python enviroment. +them yourself. You can use Anaconda or other python environment. CONTACTS diff --git a/src/diffpy/srxconfutils/config.py b/src/diffpy/srxconfutils/config.py index 77a0afe..7702d40 100644 --- a/src/diffpy/srxconfutils/config.py +++ b/src/diffpy/srxconfutils/config.py @@ -205,7 +205,7 @@ class ConfigBase(object): "fliphorizontal", { "sec": "Beamline", - "h": "filp the image horizontally", + "h": "flip the image horizontally", "n": "?", "co": True, "d": False, @@ -542,8 +542,8 @@ 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 - comming from sys.argv + :param pargs: list of string, arguments to parse, usually coming + from sys.argv """ obj = self.args.parse_args(pargs) changedargs = obj.__dict__.keys() @@ -640,7 +640,7 @@ def _postUpdateConfig(self, **kwargs): ########################################################################### def _createConfigFile(self): - """Write output config file if specfied in configuration the + """Write output config file if specified in configuration the filename is specified by self.createconfig.""" if (self.createconfig != "") and (self.createconfig != None): self.writeConfig(self.createconfig, "short") @@ -651,7 +651,7 @@ def _createConfigFile(self): return def writeConfig(self, filename, mode="short", changeconfigfile=True): - """Write config to file. the file is compatiable with python + """Write config to file. the file is compatible with python package ConfigParser. :param filename: string, name of file @@ -662,7 +662,7 @@ def writeConfig(self, filename, mode="short", changeconfigfile=True): if changeconfigfile: self.configfile = os.path.abspath(filename) self._updateSelf() - # func decide if wirte the option to config according to mode + # 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 = ( @@ -706,7 +706,7 @@ def getHeader(self, title=None, mode="full"): 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 - writen to a text file + written to a text file """ lines = [] @@ -714,7 +714,7 @@ def getHeader(self, title=None, mode="full"): self._defaultdata["headertitle"] if title == None else title ) lines.append(title) - # func decide if wirte the option to header according to mode + # 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 = ( diff --git a/src/diffpy/srxconfutils/configtraits.py b/src/diffpy/srxconfutils/configtraits.py index d71b84f..22bcd5c 100644 --- a/src/diffpy/srxconfutils/configtraits.py +++ b/src/diffpy/srxconfutils/configtraits.py @@ -255,8 +255,8 @@ class ConfigBaseTraits(HasTraits, ConfigBase): "sec": "Beamline", "header": "n", "config": "f", - "l": "Filp horizontally", - "h": "filp the image horizontally", + "l": "Flip horizontally", + "h": "flip the image horizontally", "n": "?", "co": True, "d": False, diff --git a/src/diffpy/srxconfutils/tools.py b/src/diffpy/srxconfutils/tools.py index b64baa0..78a339a 100644 --- a/src/diffpy/srxconfutils/tools.py +++ b/src/diffpy/srxconfutils/tools.py @@ -74,9 +74,9 @@ def str2bool(v): def opt2Str(opttype, optvalue): """Turn the value of one option to string, according to the option - type list of values are truned into "value1, value2, value3...". + type list of values are turned into "value1, value2, value3...". - :param opttype: string, type of opitons, for example 'str' or + :param opttype: string, type of options, for example 'str' or 'intlist' :param optvalue: value of the option :return: string, usually stored in ConfigBase.config @@ -111,7 +111,7 @@ def str2Opt(opttype, optvalue): """Convert the string to value of one option, according to the option type. - :param opttype: string, type of opitons, for example 'str' or + :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 From 081340bf48c37bb94913b61b6df29f3064de3cb8 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Tue, 9 Sep 2025 23:58:34 -0400 Subject: [PATCH 11/37] style: fix flake8 error for tools.py --- src/diffpy/srxconfutils/__init__.py | 6 +++--- src/diffpy/srxconfutils/tools.py | 12 ++++++------ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/diffpy/srxconfutils/__init__.py b/src/diffpy/srxconfutils/__init__.py index 29aa678..af5892f 100644 --- a/src/diffpy/srxconfutils/__init__.py +++ b/src/diffpy/srxconfutils/__init__.py @@ -12,9 +12,6 @@ # ############################################################################## -# some convenience imports -from diffpy.srxconfutils.config import ConfigBase - # package version from diffpy.srxconfutils.version import __version__ @@ -30,4 +27,7 @@ def test(): return test() +# silence the pyflakes syntax checker +assert __version__ or True + # End of file diff --git a/src/diffpy/srxconfutils/tools.py b/src/diffpy/srxconfutils/tools.py index 78a339a..3cb2796 100644 --- a/src/diffpy/srxconfutils/tools.py +++ b/src/diffpy/srxconfutils/tools.py @@ -59,9 +59,9 @@ def _configPropertyRW(name): read and write """ rv = property( - fget=lambda self: getattr(self.config, nm), - fset=lambda self, value: setattr(self.config, nm, value), - fdel=lambda self: delattr(self, nm), + 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 @@ -119,7 +119,7 @@ def str2Opt(opttype, optvalue): # base converter conv = StrConv(opttype) if opttype.endswith("list"): - temp = re.split("\s*,\s*", optvalue) + temp = re.split(r"\s*,\s*", optvalue) rv = list(map(conv, temp)) if len(temp) > 0 else [] else: rv = conv(optvalue) @@ -171,7 +171,7 @@ def checkCRC32(filename): """ try: fd = open(filename, "rb") - except: + except Exception: return "Read error" eachLine = fd.readline() prev = 0 @@ -190,7 +190,7 @@ def checkMD5(filename, blocksize=65536): """ try: fd = open(filename, "rb") - except: + except Exception: return "Read error" buf = fd.read(blocksize) md5 = hashlib.md5() From 320513db22df2aecb8b2a9cdce8cc57b539fc507 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Wed, 10 Sep 2025 00:19:41 -0400 Subject: [PATCH 12/37] style: fix flake8 errors for config.py --- src/diffpy/srxconfutils/config.py | 70 +++++++++++++++---------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/src/diffpy/srxconfutils/config.py b/src/diffpy/srxconfutils/config.py index 7702d40..3a6c399 100644 --- a/src/diffpy/srxconfutils/config.py +++ b/src/diffpy/srxconfutils/config.py @@ -22,34 +22,22 @@ import argparse import os -import re -import sys from configparser import ConfigParser -from functools import partial try: from collections import OrderedDict -except: +except Exception: from ordereddict import OrderedDict -from diffpy.srxconfutils.tools import ( - FakeConfigFile, - StrConv, - _configPropertyR, - _configPropertyRad, - _configPropertyRW, - opt2Str, - str2bool, - str2Opt, -) +from diffpy.srxconfutils.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. + 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 @@ -128,7 +116,10 @@ class ConfigBase(object): "sec": "Control", "config": "n", "header": "n", - "h": "create a config file according to default or current values", + "h": ( + "create a config file according to " + "default or current values" + ), "d": "", }, ], @@ -227,8 +218,12 @@ class ConfigBase(object): "sec": "Others", "config": "f", "header": "f", - "h": "mask the edge pixels, first four means the number of pixels masked in each edge \ - (left, right, top, bottom), the last one is the radius of a region masked around the corner", + "h": ( + "mask the edge pixels, first four means the " + "number of pixels masked in each edge " + "(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], }, @@ -265,7 +260,7 @@ def __init__(self, filename=None, args=None, **kwargs): # update config, first detect if a default config should be load filename = self._findDefaultConfigFile(filename, args, **kwargs) - rv = self.updateConfig(filename, args, **kwargs) + self.updateConfig(filename, args, **kwargs) return # example, overload it @@ -292,9 +287,9 @@ def _findConfigFile(self, filename=None, args=None, **kwargs): :return: name of config file if found, otherwise None """ rv = None - if filename != None: + if filename is not None: rv = filename - if args != None: + if args is not None: if ("--configfile" in args) or ("-c" in args): obj = self.args.parse_args(args) rv = obj.configfile @@ -316,9 +311,9 @@ def _findDefaultConfigFile(self, filename=None, args=None, **kwargs): return: name of config file if found, otherwise None """ rv = self._findConfigFile(filename, args, **kwargs) - if rv == None: + if rv is None: for dconf in self._defaultdata["configfile"]: - if (os.path.exists(dconf)) and (rv == None): + if (os.path.exists(dconf)) and (rv is None): rv = dconf return rv @@ -499,7 +494,7 @@ def _copyConfigtoSelf(self, optnames=None): value copied from self.config to self.*option*'. Set None to update all """ - if optnames != None: + if optnames is not None: optnames = optnames if isinstance(optnames, list) else [optnames] else: optnames = [] @@ -521,7 +516,7 @@ def _copySelftoConfig(self, optnames=None): copied from self.*option* to self.config. Set None to update all """ - if optnames != None: + if optnames is not None: optnames = optnames if isinstance(optnames, list) else [optnames] else: optnames = [] @@ -575,7 +570,7 @@ def parseConfigFile(self, filename): :param filename: str, file name of config file (include path) """ - if filename != None: + if filename is not None: filename = os.path.abspath(filename) if os.path.exists(filename): self.configfile = filename @@ -607,16 +602,16 @@ def updateConfig(self, filename=None, args=None, **kwargs): self._preUpdateConfig(**kwargs) filename = self._findConfigFile(filename, args, **kwargs) - if filename != None: + if filename is not None: rv = self.parseConfigFile(filename) - if args != None: + if args is not None: rv = self.parseArgs(args) if kwargs != {}: rv = self.parseKwargs(**kwargs) if ( - (filename == None) - and ((args == None) or (args == [])) + (filename is None) + and ((args is None) or (args == [])) and (kwargs == {}) ): rv = self._updateSelf() @@ -642,10 +637,12 @@ def _postUpdateConfig(self, **kwargs): def _createConfigFile(self): """Write output config file if specified in configuration the filename is specified by self.createconfig.""" - if (self.createconfig != "") and (self.createconfig != None): + if (self.createconfig != "") and (self.createconfig is not None): self.writeConfig(self.createconfig, "short") self.createconfig = "" - if (self.createconfigfull != "") and (self.createconfigfull != None): + if (self.createconfigfull != "") and ( + self.createconfigfull is not None + ): self.writeConfig(self.createconfigfull, "full") self.createconfigfull = "" return @@ -711,7 +708,7 @@ def getHeader(self, title=None, mode="full"): lines = [] title = "# %s #" % ( - self._defaultdata["headertitle"] if title == None else title + self._defaultdata["headertitle"] if title is None else title ) lines.append(title) # func decide if write the option to header according to mode @@ -756,7 +753,7 @@ def resetDefault(self, optnames=None): :param optnames: list of str, name of options to reset, None for all options """ - if optnames == None: + if optnames is None: optnames = self._optdata.keys() for optname in optnames: if optname in self._optdata: @@ -765,7 +762,8 @@ def resetDefault(self, optnames=None): return ########################################################################### - # IMPORTANT call this method if you want to add options as class attributes!!! + # IMPORTANT call this method if you want to add + # options as class attributes!!! @classmethod def initConfigClass(cls): From 02e4decede8b8d136f57e169cb350d7de78399af Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Wed, 17 Sep 2025 03:05:58 -0400 Subject: [PATCH 13/37] chore: use context manager for file reads and handle exceptions by raising an error --- src/diffpy/srxconfutils/tools.py | 34 +++++++++++++++----------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/src/diffpy/srxconfutils/tools.py b/src/diffpy/srxconfutils/tools.py index 3cb2796..4db7823 100644 --- a/src/diffpy/srxconfutils/tools.py +++ b/src/diffpy/srxconfutils/tools.py @@ -170,15 +170,14 @@ def checkCRC32(filename): :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() + with open(filename, "rb") as fd: + eachLine = fd.readline() + prev = 0 + while eachLine: + prev = zlib.crc32(eachLine, prev) + eachLine = fd.readline() + except OSError as e: + raise RuntimeError(f"Failed to read file {filename}") from e return prev @@ -189,15 +188,14 @@ def checkMD5(filename, blocksize=65536): :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() + with open(filename, "rb") as fd: + buf = fd.read(blocksize) + md5 = hashlib.md5() + while len(buf) > 0: + md5.update(buf) + buf = fd.read(blocksize) + except OSError as e: + raise RuntimeError(f"Failed to read file {filename}") from e return md5.hexdigest() From ab26c0129c3a31b6523da5b70931a4506ba44fab Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Wed, 17 Sep 2025 23:32:56 -0400 Subject: [PATCH 14/37] chore: update function names and write tests for them --- src/diffpy/srxconfutils/tools.py | 14 +++++++------- tests/test_tools.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 tests/test_tools.py diff --git a/src/diffpy/srxconfutils/tools.py b/src/diffpy/srxconfutils/tools.py index 4db7823..6aea723 100644 --- a/src/diffpy/srxconfutils/tools.py +++ b/src/diffpy/srxconfutils/tools.py @@ -163,7 +163,7 @@ def __next__(self): return line -def checkCRC32(filename): +def check_crc32(filename): """Calculate the crc32 value of file. :param filename: path to the file @@ -181,7 +181,7 @@ def checkCRC32(filename): return prev -def checkMD5(filename, blocksize=65536): +def check_md5(filename, blocksize=65536): """Calculate the MD5 value of file. :param filename: path to the file @@ -207,16 +207,16 @@ def checkFileVal(filename): :param filename: path to the file """ valflag = False - lastcrc = checkCRC32(filename) + lastcrc = check_crc32(filename) while not valflag: - currcrc = checkCRC32(filename) + currcrc = check_crc32(filename) if currcrc == lastcrc: - lastmd5 = checkMD5(filename) + lastmd5 = check_md5(filename) time.sleep(0.01) - currmd5 = checkMD5(filename) + currmd5 = check_md5(filename) if lastmd5 == currmd5: valflag = True else: time.sleep(0.5) - lastcrc = checkCRC32(filename) + lastcrc = check_crc32(filename) return diff --git a/tests/test_tools.py b/tests/test_tools.py new file mode 100644 index 0000000..ba180b2 --- /dev/null +++ b/tests/test_tools.py @@ -0,0 +1,30 @@ +import hashlib +import zlib + +import pytest + +from diffpy.srxconfutils import tools + + +@pytest.fixture +def temp_file(tmp_path): + """Create a temporary file with known content.""" + file_path = tmp_path / "testfile.txt" + content = b"Hello world!\nThis is a test.\n" + file_path.write_bytes(content) + return file_path, content + + +def test_check_md5(temp_file): + file_path, content = temp_file + expected_md5 = hashlib.md5(content).hexdigest() + result = tools.check_md5(file_path) + assert result == expected_md5 + + +def test_check_crc32(temp_file): + """Test the check_crc32 function.""" + file_path, content = temp_file + val = tools.check_crc32(file_path) + expected = zlib.crc32(content) + assert val == expected From 7bedccef83f055980e75aa1e16111f070ac73f43 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Wed, 17 Sep 2025 23:55:55 -0400 Subject: [PATCH 15/37] style: fix flake8 errors for configtraits.py --- src/diffpy/srxconfutils/configtraits.py | 48 ++++++++----------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/src/diffpy/srxconfutils/configtraits.py b/src/diffpy/srxconfutils/configtraits.py index 22bcd5c..ae4c598 100644 --- a/src/diffpy/srxconfutils/configtraits.py +++ b/src/diffpy/srxconfutils/configtraits.py @@ -15,18 +15,10 @@ configurations file, parse arguments from command lines, and also parse arguments passed from method/function calling inside python. -This one is similar to ConfigBase but use Traits, so every option (self.*option* is a trait) - -Note: for python 2.6, argparse and orderedDict is required, install them with easy_install +This one is similar to ConfigBase but use Traits, so every option +(self.*option* is a trait) can be observed and have a GUI interface. """ -import argparse -import os -import re -import sys -from configparser import ConfigParser -from functools import partial - from traits.api import ( Array, Bool, @@ -34,40 +26,21 @@ CInt, Directory, Enum, - Event, File, - Float, HasTraits, - Instance, - Int, List, - Property, - Range, - Str, String, - cached_property, - on_trait_change, ) -from traitsui.api import Group, Item, View from diffpy.srxconfutils.config import ConfigBase -from diffpy.srxconfutils.tools import ( - StrConv, - _configPropertyR, - _configPropertyRad, - _configPropertyRW, - opt2Str, - str2bool, - str2Opt, -) class ConfigBaseTraits(HasTraits, ConfigBase): """_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. + 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 @@ -165,7 +138,10 @@ class ConfigBaseTraits(HasTraits, ConfigBase): "sec": "Control", "config": "n", "header": "n", - "h": "create a config file according to default or current values", + "h": ( + "create a config file according to " + "default or current values" + ), "d": "", }, ], @@ -269,8 +245,12 @@ class ConfigBaseTraits(HasTraits, ConfigBase): "config": "f", "tt": "array", "l": "Mask edges", - "h": "mask the edge pixels, first four means the number of pixels masked in each edge \ - (left, right, top, bottom), the last one is the radius of a region masked around the corner", + "h": ( + "mask the edge pixels, first four means " + "the number of pixels masked in each edge " + "(left, right, top, bottom), the last one is the " + "radius of a region masked around the corner" + ), "n": 5, "d": [10, 10, 10, 10, 100], }, From b752126064eb00f88264443e2b048fe3c7532ebe Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Fri, 19 Sep 2025 16:47:30 -0400 Subject: [PATCH 16/37] chore: rename functions and move tmpfiles into conftest.py --- src/diffpy/srxconfutils/tools.py | 14 +++++++------- tests/conftest.py | 10 ++++++++++ tests/test_tools.py | 21 +++++---------------- 3 files changed, 22 insertions(+), 23 deletions(-) create mode 100644 tests/conftest.py diff --git a/src/diffpy/srxconfutils/tools.py b/src/diffpy/srxconfutils/tools.py index 6aea723..9c99e66 100644 --- a/src/diffpy/srxconfutils/tools.py +++ b/src/diffpy/srxconfutils/tools.py @@ -163,7 +163,7 @@ def __next__(self): return line -def check_crc32(filename): +def get_crc32(filename): """Calculate the crc32 value of file. :param filename: path to the file @@ -181,7 +181,7 @@ def check_crc32(filename): return prev -def check_md5(filename, blocksize=65536): +def get_md5(filename, blocksize=65536): """Calculate the MD5 value of file. :param filename: path to the file @@ -207,16 +207,16 @@ def checkFileVal(filename): :param filename: path to the file """ valflag = False - lastcrc = check_crc32(filename) + lastcrc = get_crc32(filename) while not valflag: - currcrc = check_crc32(filename) + currcrc = get_crc32(filename) if currcrc == lastcrc: - lastmd5 = check_md5(filename) + lastmd5 = get_md5(filename) time.sleep(0.01) - currmd5 = check_md5(filename) + currmd5 = get_md5(filename) if lastmd5 == currmd5: valflag = True else: time.sleep(0.5) - lastcrc = check_crc32(filename) + lastcrc = get_crc32(filename) return diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..1242fa9 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,10 @@ +import pytest + + +@pytest.fixture +def temp_file(tmp_path): + """Create a temporary file with known content.""" + file_path = tmp_path / "testfile.txt" + content = b"Hello world!\nThis is a test.\n" + file_path.write_bytes(content) + return file_path, content diff --git a/tests/test_tools.py b/tests/test_tools.py index ba180b2..864fdd4 100644 --- a/tests/test_tools.py +++ b/tests/test_tools.py @@ -1,30 +1,19 @@ import hashlib import zlib -import pytest - from diffpy.srxconfutils import tools -@pytest.fixture -def temp_file(tmp_path): - """Create a temporary file with known content.""" - file_path = tmp_path / "testfile.txt" - content = b"Hello world!\nThis is a test.\n" - file_path.write_bytes(content) - return file_path, content - - -def test_check_md5(temp_file): +def test_get_md5(temp_file): file_path, content = temp_file expected_md5 = hashlib.md5(content).hexdigest() - result = tools.check_md5(file_path) + result = tools.get_md5(file_path) assert result == expected_md5 -def test_check_crc32(temp_file): - """Test the check_crc32 function.""" +def test_get_crc32(temp_file): + """Test the get_crc32 function.""" file_path, content = temp_file - val = tools.check_crc32(file_path) + val = tools.get_crc32(file_path) expected = zlib.crc32(content) assert val == expected From 44f7f5d9f91b81f3aa95eca409ab31f275c6c7b7 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sat, 20 Sep 2025 18:02:12 -0400 Subject: [PATCH 17/37] skpkg: migrate src folder --- src/diffpy/__init__.py | 18 +++++------ src/diffpy/srxconfutils/__init__.py | 31 ++++++++----------- src/diffpy/srxconfutils/srxconfutils_app.py | 33 +++++++++++++++++++++ src/diffpy/srxconfutils/version.py | 33 ++++++++++----------- 4 files changed, 68 insertions(+), 47 deletions(-) create mode 100644 src/diffpy/srxconfutils/srxconfutils_app.py diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 815245d..64cfe45 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -1,18 +1,14 @@ #!/usr/bin/env python ############################################################################## # -# dpx.confutils by Simon J. L. Billinge group -# (c) 2013 Trustees of the Columbia University -# in the City of New York. All rights reserved. +# (c) 2025 The Trustees of Columbia University in the City of New York. +# All rights reserved. # -# File coded by: Xiaohao Yang +# File coded by: Billinge Group members and community contributors. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSENOTICE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srxconfutils/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## -"""Blank namespace package.""" - -__import__("pkg_resources").declare_namespace(__name__) - -# End of file diff --git a/src/diffpy/srxconfutils/__init__.py b/src/diffpy/srxconfutils/__init__.py index af5892f..698badd 100644 --- a/src/diffpy/srxconfutils/__init__.py +++ b/src/diffpy/srxconfutils/__init__.py @@ -1,31 +1,24 @@ #!/usr/bin/env python ############################################################################## # -# dpx.confutils by Simon J. L. Billinge group -# (c) 2013 Trustees of the Columbia University -# in the City of New York. All rights reserved. +# (c) 2025 The Trustees of Columbia University in the City of New York. +# All rights reserved. # -# File coded by: Xiaohao Yang +# File coded by: Billinge Group members. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSENOTICE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srxconfutils/graphs/contributors +# +# See LICENSE.rst for license information. # ############################################################################## +"""Configuration utilities for dpx project. -# package version -from diffpy.srxconfutils.version import __version__ - - -# unit tests -def test(): - """Execute all unit tests for the diffpy.pdfgetx package. - - Return a unittest TestResult object. - """ - from dpx.confutils.tests import test - - return test() +Part of xPDFsuite +""" +# package version +from diffpy.srxconfutils.version import __version__ # noqa # silence the pyflakes syntax checker assert __version__ or True diff --git a/src/diffpy/srxconfutils/srxconfutils_app.py b/src/diffpy/srxconfutils/srxconfutils_app.py new file mode 100644 index 0000000..f07f27b --- /dev/null +++ b/src/diffpy/srxconfutils/srxconfutils_app.py @@ -0,0 +1,33 @@ +import argparse + +from diffpy.srxconfutils.version import __version__ # noqa + + +def main(): + parser = argparse.ArgumentParser( + prog="diffpy.srxconfutils", + description=( + "Configuration utilities for dpx project. Part of xPDFsuite\n\n" + "For more information, visit: " + "https://github.com/diffpy/diffpy.srxconfutils/" + ), + formatter_class=argparse.RawDescriptionHelpFormatter, + ) + + parser.add_argument( + "--version", + action="store_true", + help="Show the program's version number and exit", + ) + + args = parser.parse_args() + + if args.version: + print(f"diffpy.srxconfutils {__version__}") + else: + # Default behavior when no arguments are given + parser.print_help() + + +if __name__ == "__main__": + main() diff --git a/src/diffpy/srxconfutils/version.py b/src/diffpy/srxconfutils/version.py index 45736e1..cfc18b7 100644 --- a/src/diffpy/srxconfutils/version.py +++ b/src/diffpy/srxconfutils/version.py @@ -1,27 +1,26 @@ #!/usr/bin/env python ############################################################################## # -# dpx.confutils by Simon J. L. Billinge group -# (c) 2013 Trustees of the Columbia University -# in the City of New York. All rights reserved. +# (c) 2025 The Trustees of Columbia University in the City of New York. +# All rights reserved. # -# File coded by: Xiaohao Yang +# File coded by: Billinge Group members. # -# See AUTHORS.txt for a list of people who contributed. -# See LICENSENOTICE.txt for license information. +# See GitHub contributions for a more detailed list of contributors. +# https://github.com/diffpy/diffpy.srxconfutils/graphs/contributors # noqa: E501 +# +# See LICENSE.rst for license information. # ############################################################################## -"""Definition of __version__ and __date__ for this package.""" - -# obtain version information -from pkg_resources import get_distribution +"""Definition of __version__.""" -_pkgname = __name__.rsplit(".", 1)[0] -__version__ = get_distribution(_pkgname).version +# We do not use the other three variables, but can be added back if needed. +# __all__ = ["__date__", "__git_commit__", "__timestamp__", "__version__"] -# we assume that tag_date was used and __version__ ends in YYYYMMDD -__date__ = ( - __version__[-8:-4] + "-" + __version__[-4:-2] + "-" + __version__[-2:] -) +# obtain version information +from importlib.metadata import PackageNotFoundError, version -# End of file +try: + __version__ = version("diffpy.srxconfutils") +except PackageNotFoundError: + __version__ = "unknown" From 5f697c0a50b8f3eb60a2655d88c53dbbc4d220b1 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sat, 20 Sep 2025 18:02:24 -0400 Subject: [PATCH 18/37] skpkg: migrate tests folder --- tests/conftest.py | 18 ++++++++++++++++++ tests/test_version.py | 10 ++++++++++ 2 files changed, 28 insertions(+) create mode 100644 tests/test_version.py diff --git a/tests/conftest.py b/tests/conftest.py index 1242fa9..a3114ea 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,24 @@ +import json +from pathlib import Path + import pytest +@pytest.fixture +def user_filesystem(tmp_path): + base_dir = Path(tmp_path) + home_dir = base_dir / "home_dir" + home_dir.mkdir(parents=True, exist_ok=True) + cwd_dir = base_dir / "cwd_dir" + cwd_dir.mkdir(parents=True, exist_ok=True) + + home_config_data = {"username": "home_username", "email": "home@email.com"} + with open(home_dir / "diffpyconfig.json", "w") as f: + json.dump(home_config_data, f) + + yield tmp_path + + @pytest.fixture def temp_file(tmp_path): """Create a temporary file with known content.""" diff --git a/tests/test_version.py b/tests/test_version.py new file mode 100644 index 0000000..e2d2c24 --- /dev/null +++ b/tests/test_version.py @@ -0,0 +1,10 @@ +"""Unit tests for __version__.py.""" + +import diffpy.srxconfutils # noqa + + +def test_package_version(): + """Ensure the package version is defined and not set to the initial + placeholder.""" + assert hasattr(diffpy.srxconfutils, "__version__") + assert diffpy.srxconfutils.__version__ != "0.0.0" From 24338ce4aa41fa493dafe6fd82d2ea32329cbb29 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sat, 20 Sep 2025 18:07:57 -0400 Subject: [PATCH 19/37] skpkg: list dependencies in requirements folder --- requirements/conda.txt | 3 +++ requirements/docs.txt | 5 +++++ requirements/pip.txt | 1 - requirements/tests.txt | 6 ++++++ 4 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 requirements/conda.txt create mode 100644 requirements/docs.txt create mode 100644 requirements/tests.txt diff --git a/requirements/conda.txt b/requirements/conda.txt new file mode 100644 index 0000000..73b057e --- /dev/null +++ b/requirements/conda.txt @@ -0,0 +1,3 @@ +numpy +traits +traitsui diff --git a/requirements/docs.txt b/requirements/docs.txt new file mode 100644 index 0000000..5f34c6e --- /dev/null +++ b/requirements/docs.txt @@ -0,0 +1,5 @@ +sphinx +sphinx_rtd_theme +sphinx-copybutton +doctr +m2r diff --git a/requirements/pip.txt b/requirements/pip.txt index 59ebc00..73b057e 100644 --- a/requirements/pip.txt +++ b/requirements/pip.txt @@ -1,4 +1,3 @@ numpy -configparser traits traitsui diff --git a/requirements/tests.txt b/requirements/tests.txt new file mode 100644 index 0000000..a727786 --- /dev/null +++ b/requirements/tests.txt @@ -0,0 +1,6 @@ +flake8 +pytest +codecov +coverage +pytest-cov +pytest-env From 6de173e80918b9cb45d4998ab8efafd4f287acd3 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sat, 20 Sep 2025 18:14:49 -0400 Subject: [PATCH 20/37] skpkg: add CI --- .github/workflows/tests-on-pr.yml | 16 ++++++ .gitignore | 89 ++++++++++++++++++++++++------- 2 files changed, 85 insertions(+), 20 deletions(-) create mode 100644 .github/workflows/tests-on-pr.yml diff --git a/.github/workflows/tests-on-pr.yml b/.github/workflows/tests-on-pr.yml new file mode 100644 index 0000000..aa251fc --- /dev/null +++ b/.github/workflows/tests-on-pr.yml @@ -0,0 +1,16 @@ +name: Tests on PR + +on: + pull_request: + workflow_dispatch: + +jobs: + tests-on-pr: + uses: scikit-package/release-scripts/.github/workflows/_tests-on-pr-no-codecov.yml@v0 + with: + project: diffpy.srxconfutils + c_extension: false + headless: false + run: | + conda install pre-commit + pre-commit run --all-files diff --git a/.gitignore b/.gitignore index b9f9b7d..099e294 100644 --- a/.gitignore +++ b/.gitignore @@ -1,44 +1,93 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ *.py[cod] +*$py.class # C extensions *.so -# Packages -*.egg -*.egg-info -dist -build -eggs -parts -bin -var -sdist -temp -develop-eggs +# Distribution / packaging +.Python +env/ +build/ +_build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +venv/ +*.egg-info/ .installed.cfg -lib -lib64 -tags +*.egg +bin/ +temp/ +tags/ errors.err +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + # Installer logs pip-log.txt +pip-delete-this-directory.txt MANIFEST # Unit test / coverage reports +htmlcov/ +.tox/ .coverage -.tox +.coverage.* +.cache nosetests.xml +coverage.xml +*,cover +.hypothesis/ # Translations *.mo +*.pot # Mr Developer .mr.developer.cfg .project .pydevproject -.settings -# version information -setup.cfg -/dpx/confutils/version.cfg +# Django stuff: +*.log + +# Sphinx documentation +docs/build/ +docs/source/generated/ + +# pytest +.pytest_cache/ + +# PyBuilder +target/ + +# Editor files +# mac +.DS_Store +*~ + +# vim +*.swp +*.swo + +# pycharm +.idea/ + +# VSCode +.vscode/ + +# Ipython Notebook +.ipynb_checkpoints From 7f2205dd5293ffddbbb30d32e2b355d4769e8141 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sat, 20 Sep 2025 18:15:14 -0400 Subject: [PATCH 21/37] skpkg: add pyproject.toml --- pyproject.toml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 918ff52..80824ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,10 +6,10 @@ build-backend = "setuptools.build_meta" name = "diffpy.srxconfutils" dynamic=['version', 'dependencies'] authors = [ - { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, + { name="Simon J.L. Billinge group", email="sb2896@columbia.edu" }, ] maintainers = [ - { name="Simon J.L. Billinge group", email="simon.billinge@gmail.com" }, + { name="Simon J.L. Billinge group", email="sb2896@columbia.edu" }, ] description = "Configuration utilities for dpx project. Part of xPDFsuite" keywords = ['diffpy', 'pdf', 'data interpretation'] @@ -48,6 +48,9 @@ include = ["*"] # package names should match these glob patterns (["*"] by defa exclude = [] # exclude packages matching these glob patterns (empty by default) namespaces = false # to disable scanning PEP 420 namespaces (true by default) +[project.scripts] +diffpy-srxconfutils = "diffpy.srxconfutils.app:main" + [tool.setuptools.dynamic] dependencies = {file = ["requirements/pip.txt"]} From 14adc8d06300b7c59fc4e8a7ccaab435812a09cb Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sun, 21 Sep 2025 01:29:51 -0400 Subject: [PATCH 22/37] skpkg: add the rest of the github files --- .github/ISSUE_TEMPLATE/bug_feature.md | 16 ++ .github/ISSUE_TEMPLATE/release_checklist.md | 46 +++++ .../pull_request_template.md | 15 ++ .../workflows/build-wheel-release-upload.yml | 163 ++++++++++++++++++ .github/workflows/check-news-item.yml | 12 ++ .../matrix-and-codecov-on-merge-to-main.yml | 19 ++ .github/workflows/release-github.yml | 163 ++++++++++++++++++ 7 files changed, 434 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_feature.md create mode 100644 .github/ISSUE_TEMPLATE/release_checklist.md create mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md create mode 100644 .github/workflows/build-wheel-release-upload.yml create mode 100644 .github/workflows/check-news-item.yml create mode 100644 .github/workflows/matrix-and-codecov-on-merge-to-main.yml create mode 100644 .github/workflows/release-github.yml diff --git a/.github/ISSUE_TEMPLATE/bug_feature.md b/.github/ISSUE_TEMPLATE/bug_feature.md new file mode 100644 index 0000000..b3454de --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_feature.md @@ -0,0 +1,16 @@ +--- +name: Bug Report or Feature Request +about: Report a bug or suggest a new feature! +title: "" +labels: "" +assignees: "" +--- + +### Problem + + + +### Proposed solution diff --git a/.github/ISSUE_TEMPLATE/release_checklist.md b/.github/ISSUE_TEMPLATE/release_checklist.md new file mode 100644 index 0000000..56bcd01 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/release_checklist.md @@ -0,0 +1,46 @@ +--- +name: Release +about: Checklist and communication channel for PyPI and GitHub release +title: "Ready for PyPI/GitHub release" +labels: "release" +assignees: "" +--- + +### PyPI/GitHub rc-release preparation checklist: + +- [ ] All PRs/issues attached to the release are merged. +- [ ] All the badges on the README are passing. +- [ ] License information is verified as correct. If you are unsure, please comment below. +- [ ] Locally rendered documentation contains all appropriate pages, including API references (check no modules are + missing), tutorials, and other human-written text is up-to-date with any changes in the code. +- [ ] Installation instructions in the README, documentation, and the website are updated. +- [ ] Successfully run any tutorial examples or do functional testing with the latest Python version. +- [ ] Grammar and writing quality are checked (no typos). +- [ ] Install `pip install build twine`, run `python -m build` and `twine check dist/*` to ensure that the package can be built and is correctly formatted for PyPI release. + +Please tag the maintainer (e.g., @username) in the comment here when you are ready for the PyPI/GitHub release. Include any additional comments necessary, such as version information and details about the pre-release here: + +### PyPI/GitHub full-release preparation checklist: + +- [ ] Create a new conda environment and install the rc from PyPI (`pip install ==??`) +- [ ] License information on PyPI is correct. +- [ ] Docs are deployed successfully to `https:///`. +- [ ] Successfully run all tests, tutorial examples or do functional testing. + +Please let the maintainer know that all checks are done and the package is ready for full release. + +### conda-forge release preparation checklist: + + + +- [ ] Ensure that the full release has appeared on PyPI successfully. +- [ ] New package dependencies listed in `conda.txt` and `tests.txt` are added to `meta.yaml` in the feedstock. +- [ ] Close any open issues on the feedstock. Reach out to the maintainer if you have questions. +- [ ] Tag the maintainer for conda-forge release. + +### Post-release checklist + + + +- [ ] Run tutorial examples and conduct functional testing using the installation guide in the README. Attach screenshots/results as comments. +- [ ] Documentation (README, tutorials, API references, and websites) is deployed without broken links or missing figures. diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md new file mode 100644 index 0000000..1099d86 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -0,0 +1,15 @@ +### What problem does this PR address? + + + +### What should the reviewer(s) do? + + + + diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml new file mode 100644 index 0000000..f809eb3 --- /dev/null +++ b/.github/workflows/build-wheel-release-upload.yml @@ -0,0 +1,163 @@ +name: Release on GitHub + +on: + workflow_call: + secrets: + PAT_TOKEN: + description: "GitHub Personal Access Token" + required: true + +env: + TAG: ${{ github.ref_name }} + +defaults: + run: + shell: bash {0} + +jobs: + prepare-release: + if: ${{ ! contains(github.ref, 'rc') }} + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + ref: main + + - name: Update CHANGELOG + run: | + wget https://raw.githubusercontent.com/scikit-package/release-scripts/v0/.github/workflows/update-changelog.py + python update-changelog.py "$TAG" + rm update-changelog.py + + - name: Commit updated CHANGELOG.rst + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add . + if ! git diff --cached --quiet; then + git commit -m "update changelog for $TAG" + git push origin main + else + echo "No CHANGELOG.rst changes" + fi + + - name: New tag + run: | + git fetch --tags + git tag -d "$TAG" 2>/dev/null || true + git push origin ":$TAG" 2>/dev/null || true + git tag "$TAG" + git push origin "$TAG" + + - name: Get CHANGELOG.txt + run: | + wget https://raw.githubusercontent.com/scikit-package/release-scripts/v0/.github/workflows/get-latest-changelog.py + python get-latest-changelog.py "$TAG" + rm get-latest-changelog.py + + - name: Upload changelog.txt + uses: actions/upload-artifact@v4 + with: + name: changelog + path: CHANGELOG.txt + + release: + needs: [prepare-release] + if: always() + runs-on: ubuntu-latest + env: + REPO_FULL: ${{ github.repository }} + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} + steps: + - name: Check prepare release + run: | + if [[ ${{ needs.prepare-release.result }} != 'success' && "$TAG" != *rc* ]]; then + echo "::error::Skipping release job because prepare-release failed" + exit 78 + fi + echo "Continuing with release job" + + - name: Download built wheels + uses: actions/download-artifact@v4 + with: + path: dist/ + + - name: Download changelog + if: ${{ needs.prepare-release.result == 'success' }} + uses: actions/download-artifact@v4 + with: + name: changelog + path: . + + - name: Download instructions + uses: actions/download-artifact@v4 + with: + name: instructions + path: . + + - name: Zip wheels and instructions into dist/pdfgetx-$TAG-wheels.zip + run: | + mkdir -p dist + find dist -type f -name '*.whl' | zip -j dist/pdfgetx-"$TAG"-wheels.zip -@ + zip -j dist/pdfgetx-"$TAG"-wheels.zip INSTRUCTIONS.txt + + - name: Prepare release metadata + id: meta + run: | + if [[ "$TAG" == *rc* ]]; then + PRERELEASE=true + TITLE="Pre-release $TAG" + BODY_RAW="Changelog: https://github.com/$REPO_FULL/commits/$TAG" + else + PRERELEASE=false + TITLE="Release $TAG" + BODY_RAW=$( payload.json + + echo "Release metadata:" + cat payload.json + + - name: Create GitHub Release + id: create_release + run: | + set -euo pipefail + + HTTP_STATUS=$( + curl --silent --output resp.json --write-out "%{http_code}" \ + -X POST "https://api.github.com/repos/$REPO_FULL/releases" \ + -H "Accept: application/vnd.github+json" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $PAT_TOKEN" \ + --data @payload.json + ) + if [[ "$HTTP_STATUS" -ne 201 ]]; then + echo "::error::Failed to create release (status $HTTP_STATUS)" + exit 1 + fi + + UPLOAD_URL=$(jq -r .upload_url resp.json | sed 's/{.*}//') + echo "upload_url=$UPLOAD_URL" >> $GITHUB_OUTPUT + + - name: Upload pdfgetx-$TAG-wheels.zip + if: steps.create_release.outputs.upload_url != '' + run: | + FILE=dist/pdfgetx-$TAG-wheels.zip + echo "Uploading asset: $FILE" + curl --silent --fail --data-binary @"$FILE" \ + -H "Content-Type: application/zip" \ + -H "Authorization: Bearer $PAT_TOKEN" \ + "${{ steps.create_release.outputs.upload_url }}?name=$(basename "$FILE")" diff --git a/.github/workflows/check-news-item.yml b/.github/workflows/check-news-item.yml new file mode 100644 index 0000000..4857a2c --- /dev/null +++ b/.github/workflows/check-news-item.yml @@ -0,0 +1,12 @@ +name: Check for News + +on: + pull_request_target: + branches: + - main + +jobs: + check-news-item: + uses: scikit-package/release-scripts/.github/workflows/_check-news-item.yml@v0 + with: + project: diffpy.srxconfutils diff --git a/.github/workflows/matrix-and-codecov-on-merge-to-main.yml b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml new file mode 100644 index 0000000..4745dc9 --- /dev/null +++ b/.github/workflows/matrix-and-codecov-on-merge-to-main.yml @@ -0,0 +1,19 @@ +name: CI + +on: + push: + branches: + - main + release: + types: + - prereleased + - published + workflow_dispatch: + +jobs: + matrix-coverage: + uses: scikit-package/release-scripts/.github/workflows/_matrix-no-codecov-on-merge-to-main.yml@v0 + with: + project: diffpy.srxconfutils + c_extension: false + headless: false diff --git a/.github/workflows/release-github.yml b/.github/workflows/release-github.yml new file mode 100644 index 0000000..f809eb3 --- /dev/null +++ b/.github/workflows/release-github.yml @@ -0,0 +1,163 @@ +name: Release on GitHub + +on: + workflow_call: + secrets: + PAT_TOKEN: + description: "GitHub Personal Access Token" + required: true + +env: + TAG: ${{ github.ref_name }} + +defaults: + run: + shell: bash {0} + +jobs: + prepare-release: + if: ${{ ! contains(github.ref, 'rc') }} + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + fetch-depth: 0 + ref: main + + - name: Update CHANGELOG + run: | + wget https://raw.githubusercontent.com/scikit-package/release-scripts/v0/.github/workflows/update-changelog.py + python update-changelog.py "$TAG" + rm update-changelog.py + + - name: Commit updated CHANGELOG.rst + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add . + if ! git diff --cached --quiet; then + git commit -m "update changelog for $TAG" + git push origin main + else + echo "No CHANGELOG.rst changes" + fi + + - name: New tag + run: | + git fetch --tags + git tag -d "$TAG" 2>/dev/null || true + git push origin ":$TAG" 2>/dev/null || true + git tag "$TAG" + git push origin "$TAG" + + - name: Get CHANGELOG.txt + run: | + wget https://raw.githubusercontent.com/scikit-package/release-scripts/v0/.github/workflows/get-latest-changelog.py + python get-latest-changelog.py "$TAG" + rm get-latest-changelog.py + + - name: Upload changelog.txt + uses: actions/upload-artifact@v4 + with: + name: changelog + path: CHANGELOG.txt + + release: + needs: [prepare-release] + if: always() + runs-on: ubuntu-latest + env: + REPO_FULL: ${{ github.repository }} + PAT_TOKEN: ${{ secrets.PAT_TOKEN }} + steps: + - name: Check prepare release + run: | + if [[ ${{ needs.prepare-release.result }} != 'success' && "$TAG" != *rc* ]]; then + echo "::error::Skipping release job because prepare-release failed" + exit 78 + fi + echo "Continuing with release job" + + - name: Download built wheels + uses: actions/download-artifact@v4 + with: + path: dist/ + + - name: Download changelog + if: ${{ needs.prepare-release.result == 'success' }} + uses: actions/download-artifact@v4 + with: + name: changelog + path: . + + - name: Download instructions + uses: actions/download-artifact@v4 + with: + name: instructions + path: . + + - name: Zip wheels and instructions into dist/pdfgetx-$TAG-wheels.zip + run: | + mkdir -p dist + find dist -type f -name '*.whl' | zip -j dist/pdfgetx-"$TAG"-wheels.zip -@ + zip -j dist/pdfgetx-"$TAG"-wheels.zip INSTRUCTIONS.txt + + - name: Prepare release metadata + id: meta + run: | + if [[ "$TAG" == *rc* ]]; then + PRERELEASE=true + TITLE="Pre-release $TAG" + BODY_RAW="Changelog: https://github.com/$REPO_FULL/commits/$TAG" + else + PRERELEASE=false + TITLE="Release $TAG" + BODY_RAW=$( payload.json + + echo "Release metadata:" + cat payload.json + + - name: Create GitHub Release + id: create_release + run: | + set -euo pipefail + + HTTP_STATUS=$( + curl --silent --output resp.json --write-out "%{http_code}" \ + -X POST "https://api.github.com/repos/$REPO_FULL/releases" \ + -H "Accept: application/vnd.github+json" \ + -H "Content-Type: application/json" \ + -H "Authorization: Bearer $PAT_TOKEN" \ + --data @payload.json + ) + if [[ "$HTTP_STATUS" -ne 201 ]]; then + echo "::error::Failed to create release (status $HTTP_STATUS)" + exit 1 + fi + + UPLOAD_URL=$(jq -r .upload_url resp.json | sed 's/{.*}//') + echo "upload_url=$UPLOAD_URL" >> $GITHUB_OUTPUT + + - name: Upload pdfgetx-$TAG-wheels.zip + if: steps.create_release.outputs.upload_url != '' + run: | + FILE=dist/pdfgetx-$TAG-wheels.zip + echo "Uploading asset: $FILE" + curl --silent --fail --data-binary @"$FILE" \ + -H "Content-Type: application/zip" \ + -H "Authorization: Bearer $PAT_TOKEN" \ + "${{ steps.create_release.outputs.upload_url }}?name=$(basename "$FILE")" From 36f9ec3ac5db8c417a5f443a99934fdb9abc704c Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sun, 21 Sep 2025 01:32:59 -0400 Subject: [PATCH 23/37] chore: change pdfgetx to srxconfutils in the .github workflow files --- .github/workflows/build-wheel-release-upload.yml | 10 +++++----- .github/workflows/release-github.yml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml index f809eb3..3e78d1d 100644 --- a/.github/workflows/build-wheel-release-upload.yml +++ b/.github/workflows/build-wheel-release-upload.yml @@ -98,11 +98,11 @@ jobs: name: instructions path: . - - name: Zip wheels and instructions into dist/pdfgetx-$TAG-wheels.zip + - name: Zip wheels and instructions into dist/srxconfutils-$TAG-wheels.zip run: | mkdir -p dist - find dist -type f -name '*.whl' | zip -j dist/pdfgetx-"$TAG"-wheels.zip -@ - zip -j dist/pdfgetx-"$TAG"-wheels.zip INSTRUCTIONS.txt + find dist -type f -name '*.whl' | zip -j dist/srxconfutils-"$TAG"-wheels.zip -@ + zip -j dist/srxconfutils-"$TAG"-wheels.zip INSTRUCTIONS.txt - name: Prepare release metadata id: meta @@ -152,10 +152,10 @@ jobs: UPLOAD_URL=$(jq -r .upload_url resp.json | sed 's/{.*}//') echo "upload_url=$UPLOAD_URL" >> $GITHUB_OUTPUT - - name: Upload pdfgetx-$TAG-wheels.zip + - name: Upload srxconfutils-$TAG-wheels.zip if: steps.create_release.outputs.upload_url != '' run: | - FILE=dist/pdfgetx-$TAG-wheels.zip + FILE=dist/srxconfutils-$TAG-wheels.zip echo "Uploading asset: $FILE" curl --silent --fail --data-binary @"$FILE" \ -H "Content-Type: application/zip" \ diff --git a/.github/workflows/release-github.yml b/.github/workflows/release-github.yml index f809eb3..3e78d1d 100644 --- a/.github/workflows/release-github.yml +++ b/.github/workflows/release-github.yml @@ -98,11 +98,11 @@ jobs: name: instructions path: . - - name: Zip wheels and instructions into dist/pdfgetx-$TAG-wheels.zip + - name: Zip wheels and instructions into dist/srxconfutils-$TAG-wheels.zip run: | mkdir -p dist - find dist -type f -name '*.whl' | zip -j dist/pdfgetx-"$TAG"-wheels.zip -@ - zip -j dist/pdfgetx-"$TAG"-wheels.zip INSTRUCTIONS.txt + find dist -type f -name '*.whl' | zip -j dist/srxconfutils-"$TAG"-wheels.zip -@ + zip -j dist/srxconfutils-"$TAG"-wheels.zip INSTRUCTIONS.txt - name: Prepare release metadata id: meta @@ -152,10 +152,10 @@ jobs: UPLOAD_URL=$(jq -r .upload_url resp.json | sed 's/{.*}//') echo "upload_url=$UPLOAD_URL" >> $GITHUB_OUTPUT - - name: Upload pdfgetx-$TAG-wheels.zip + - name: Upload srxconfutils-$TAG-wheels.zip if: steps.create_release.outputs.upload_url != '' run: | - FILE=dist/pdfgetx-$TAG-wheels.zip + FILE=dist/srxconfutils-$TAG-wheels.zip echo "Uploading asset: $FILE" curl --silent --fail --data-binary @"$FILE" \ -H "Content-Type: application/zip" \ From 9691ebc607bedda4d6b6f830407d792ce2595aef Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Tue, 23 Sep 2025 14:56:18 -0400 Subject: [PATCH 24/37] chore: fix copyright dates and delete dpx --- src/diffpy/__init__.py | 4 ++-- src/diffpy/srxconfutils/__init__.py | 6 +++--- src/diffpy/srxconfutils/version.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/diffpy/__init__.py b/src/diffpy/__init__.py index 64cfe45..2af95ff 100644 --- a/src/diffpy/__init__.py +++ b/src/diffpy/__init__.py @@ -1,10 +1,10 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2025 The Trustees of Columbia University in the City of New York. +# (c) 2013-2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # -# File coded by: Billinge Group members and community contributors. +# File coded by: Xiaohao Yang and Billinge Group members. # # See GitHub contributions for a more detailed list of contributors. # https://github.com/diffpy/diffpy.srxconfutils/graphs/contributors diff --git a/src/diffpy/srxconfutils/__init__.py b/src/diffpy/srxconfutils/__init__.py index 698badd..e07b832 100644 --- a/src/diffpy/srxconfutils/__init__.py +++ b/src/diffpy/srxconfutils/__init__.py @@ -1,10 +1,10 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2025 The Trustees of Columbia University in the City of New York. +# (c) 2013-2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # -# File coded by: Billinge Group members. +# File coded by: Xiaohao Yang and Billinge Group members. # # See GitHub contributions for a more detailed list of contributors. # https://github.com/diffpy/diffpy.srxconfutils/graphs/contributors @@ -12,7 +12,7 @@ # See LICENSE.rst for license information. # ############################################################################## -"""Configuration utilities for dpx project. +"""Configuration utilities for project. Part of xPDFsuite """ diff --git a/src/diffpy/srxconfutils/version.py b/src/diffpy/srxconfutils/version.py index cfc18b7..dbd80dd 100644 --- a/src/diffpy/srxconfutils/version.py +++ b/src/diffpy/srxconfutils/version.py @@ -1,10 +1,10 @@ #!/usr/bin/env python ############################################################################## # -# (c) 2025 The Trustees of Columbia University in the City of New York. +# (c) 2013-2025 The Trustees of Columbia University in the City of New York. # All rights reserved. # -# File coded by: Billinge Group members. +# File coded by: Xiaohao Yang and Billinge Group members. # # See GitHub contributions for a more detailed list of contributors. # https://github.com/diffpy/diffpy.srxconfutils/graphs/contributors # noqa: E501 From a4288da8506370d4e9d0aa92b4ebd7fe33a0fc01 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Tue, 30 Sep 2025 01:26:57 -0400 Subject: [PATCH 25/37] skpkg: add config files readthedocs, codecov --- .codecov.yml | 14 ++++++++++++++ .readthedocs.yaml | 13 +++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 .codecov.yml create mode 100644 .readthedocs.yaml diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..4af5eb2 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,14 @@ +coverage: + status: + project: # more options at https://docs.codecov.com/docs/commit-status + default: + target: auto # use the coverage from the base commit, fail if coverage is lower + threshold: 0% # allow the coverage to drop by + +comment: + layout: " diff, flags, files" + behavior: default + require_changes: false + require_base: false # [true :: must have a base report to post] + require_head: false # [true :: must have a head report to post] + hide_project_coverage: false # [true :: only show coverage on the git diff aka patch coverage] diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..aaa8889 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,13 @@ +version: 2 + +build: + os: "ubuntu-22.04" + tools: + python: "latest" + +python: + install: + - requirements: requirements/docs.txt + +sphinx: + configuration: docs/source/conf.py From e00efde8d56134f9a9ce8703f15b77a5480ee62b Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Wed, 1 Oct 2025 02:40:22 -0400 Subject: [PATCH 26/37] skpkg: add public facing files like authors, changelog, code-of-conduct, license --- AUTHORS.rst | 11 ++++ CHANGELOG.rst | 5 ++ CODE-OF-CONDUCT.rst | 133 ++++++++++++++++++++++++++++++++++++++++++++ LICENSENOTICE.rst | 10 ++++ 4 files changed, 159 insertions(+) create mode 100644 AUTHORS.rst create mode 100644 CHANGELOG.rst create mode 100644 CODE-OF-CONDUCT.rst create mode 100644 LICENSENOTICE.rst diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..05b7097 --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,11 @@ +Authors +======= + +Xiaohao Yang +Billinge Group members + +Contributors +------------ + +For a list of contributors, visit +https://github.com/diffpy/diffpy.srxconfutils/graphs/contributors diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..f29d3b5 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,5 @@ +============= +Release notes +============= + +.. current developments diff --git a/CODE-OF-CONDUCT.rst b/CODE-OF-CONDUCT.rst new file mode 100644 index 0000000..e8199ca --- /dev/null +++ b/CODE-OF-CONDUCT.rst @@ -0,0 +1,133 @@ +===================================== + Contributor Covenant Code of Conduct +===================================== + +Our Pledge +---------- + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socioeconomic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +Our Standards +------------- + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +Enforcement Responsibilities +---------------------------- + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +Scope +----- + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +Enforcement +----------- + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +sb2896@columbia.edu. All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +Enforcement Guidelines +---------------------- + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +1. Correction +**************** + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +2. Warning +************* + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +3. Temporary Ban +****************** + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +4. Permanent Ban +****************** + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +Attribution +----------- + +This Code of Conduct is adapted from the `Contributor Covenant `_. + +Community Impact Guidelines were inspired by `Mozilla's code of conduct enforcement ladder `_. + +For answers to common questions about this code of conduct, see the `FAQ `_. `Translations are available `_ diff --git a/LICENSENOTICE.rst b/LICENSENOTICE.rst new file mode 100644 index 0000000..db80319 --- /dev/null +++ b/LICENSENOTICE.rst @@ -0,0 +1,10 @@ +Use of this software is subject to and permitted only under a separate, +written Use License granted by Columbia University. If you or your employer +is not a party to such an agreement, then your use of this software is +prohibited. If you don’t know whether or not your anticipated use is under +a license, you must contact Prof. Simon Billinge at sb2896@columbia.edu. +Use of this software without a license is prohibited. + +Copyright 2009-2025, Trustees of Columbia University in the City of New York. + +For more information please email Prof. Simon Billinge at sb2896@columbia.edu From 16f5d7cee6158d9ecab404ad9f6bd7bc17eb5260 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Wed, 1 Oct 2025 02:42:03 -0400 Subject: [PATCH 27/37] chore: delete old authors and licensenotice files --- AUTHORS.txt | 7 ------- LICENSENOTICE.txt | 10 ---------- 2 files changed, 17 deletions(-) delete mode 100644 AUTHORS.txt delete mode 100644 LICENSENOTICE.txt diff --git a/AUTHORS.txt b/AUTHORS.txt deleted file mode 100644 index cddf273..0000000 --- a/AUTHORS.txt +++ /dev/null @@ -1,7 +0,0 @@ -This code is developed by: - - Xiaohao Yang - -This code was developed as part of the xPDFsuite project to create software -and tools for general researchers to use PDF in their work. For more -information on the DiffPy project email sb2896@columbia.edu diff --git a/LICENSENOTICE.txt b/LICENSENOTICE.txt deleted file mode 100644 index 8fc8b60..0000000 --- a/LICENSENOTICE.txt +++ /dev/null @@ -1,10 +0,0 @@ -Use of this software is subject to and permitted only under a separate, -written Use License granted by Columbia University. If you or your employer -is not a party to such an agreement, then your use of this software is -prohibited. If you don’t know whether or not your anticipated use is under -a license, you must contact Prof. Simon Billinge at sb2896@columbia.edu. -Use of this software without a license is prohibited. - -Copyright 2009-2016, Trustees of Columbia University in the City of New York. - -For more information please email Prof. Simon Billinge at sb2896@columbia.edu From de24e9a064b6b8bb3a987dd15f03274b59afb043 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Wed, 1 Oct 2025 02:45:57 -0400 Subject: [PATCH 28/37] skpkg: add MANIFEST.in --- MANIFEST.in | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 982d898..f1a78ee 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,12 @@ -recursive-include dpx * -prune doc -exclude MANIFEST.in +graft src +graft tests +graft requirements + +include AUTHORS.rst LICENSE*.rst README.rst + +# Exclude all bytecode files and __pycache__ directories +global-exclude *.py[cod] # Exclude all .pyc, .pyo, and .pyd files. +global-exclude .DS_Store # Exclude Mac filesystem artifacts. +global-exclude __pycache__ # Exclude Python cache directories. +global-exclude .git* # Exclude git files and directories. +global-exclude .idea # Exclude PyCharm project settings. From 9f2927d5d0bde0178bc9670d41514fb9aa13d542 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Wed, 1 Oct 2025 02:46:28 -0400 Subject: [PATCH 29/37] skpkg: add cookiecutter.json --- cookiecutter.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 cookiecutter.json diff --git a/cookiecutter.json b/cookiecutter.json new file mode 100644 index 0000000..629bacd --- /dev/null +++ b/cookiecutter.json @@ -0,0 +1,18 @@ +{ + "maintainer_name": "Simon J.L. Billinge group", + "maintainer_email": "sb2896@columbia.edu", + "maintainer_github_username": "sbillinge", + "contributors": "Billinge Group members", + "license_holders": "The Trustees of Columbia University in the City of New York", + "project_name": "diffpy.srxconfutils", + "github_username_or_orgname": "diffpy", + "github_repo_name": "diffpy.srxconfutils", + "conda_pypi_package_dist_name": "diffpy.srxconfutils", + "package_dir_name": "diffpy.srxconfutils", + "project_short_description": "Configuration utilities for project. Part of xPDFsuite", + "project_keywords": "diffpy, pdf, data interpretation", + "minimum_supported_python_version": "3.11", + "maximum_supported_python_version": "3.13", + "project_needs_c_code_compiled": "No", + "project_has_gui_tests": "No" +} From 4bf6b4a0bf2ba62cdd477f899bed16c6b4255651 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Wed, 1 Oct 2025 02:48:32 -0400 Subject: [PATCH 30/37] chore: add indent to author names in authors.rst --- AUTHORS.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 05b7097..bbc8c0d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,8 +1,8 @@ Authors ======= -Xiaohao Yang -Billinge Group members + Xiaohao Yang + Billinge Group members Contributors ------------ From 4debd5ca4a1f8c3c5ee4d68d600c778fb51498f3 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Fri, 3 Oct 2025 12:55:15 -0400 Subject: [PATCH 31/37] skpkg: migrate readme.rst --- README.rst | 94 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 73 insertions(+), 21 deletions(-) diff --git a/README.rst b/README.rst index b9418be..7baf7cc 100644 --- a/README.rst +++ b/README.rst @@ -1,36 +1,88 @@ -dpx.confutils -======================================================================== +|Icon| |title|_ +=============== -Package for processing configurations +.. |title| replace:: diffpy.srxconfutils +.. _title: https://diffpy.github.io/diffpy.srxconfutils +.. |Icon| image:: https://avatars.githubusercontent.com/diffpy + :target: https://diffpy.github.io/diffpy.srxconfutils + :height: 100px -REQUIREMENTS ------------------------------------------------------------------------- +|PythonVersion| |PR| -The dpx.confutils requires Python 2.7 and the following software: +|Black| |Tracking| -* ``numpy`` -* ``traits`` +.. |Black| image:: https://img.shields.io/badge/code_style-black-black + :target: https://github.com/psf/black +.. |PR| image:: https://img.shields.io/badge/PR-Welcome-29ab47ff + :target: https://github.com/diffpy/diffpy.srxconfutils/pulls -INSTALLATION ------------------------------------------------------------------------- +.. |PythonVersion| image:: https://img.shields.io/badge/python-3.11%20|%203.12%20|%203.13-blue -We are going to release conda package for all platform. For general user -please use the installation file and install software. For developor, -you can install dpx.confutils using +.. |Tracking| image:: https://img.shields.io/badge/issue_tracking-github-blue + :target: https://github.com/diffpy/diffpy.srxconfutils/issues - python setup.py install +Configuration utilities for diffpy project. Part of xPDFsuite. -Note: the dependency is not specified in the setup.py. You need to install -them yourself. You can use Anaconda or other python environment. +* LONGER DESCRIPTION HERE +For more information about the diffpy.srxconfutils library, please consult our `online documentation `_. -CONTACTS ------------------------------------------------------------------------- +Citation +-------- -For more information on diffpy.Structure please visit the project web-page +If you use diffpy.srxconfutils in a scientific publication, we would like you to cite this package as -http://www.diffpy.org/ + diffpy.srxconfutils Package, https://github.com/diffpy/diffpy.srxconfutils -or email Prof. Simon Billinge at sb2896@columbia.edu. +Installation +------------ + +Assuming you have a wheel file in the current working directory, in an active conda environment please type + + pip install ./diffpy.srxconfutils-VERSION.whl + +where you replace VERSION with the actual version you have so the command matches the filename of the +wheel file you have. + +The commands to create and activate the conda environment with name "conf-env" is + + conda create -n conf-env python=3.13 + conda activate conf-env + +If you don't have conda installed, we recomment you install `miniconda +`_ +To install this software from a Python wheel distribution format execute + + pip install ./diffpy.srxconfutils-VERSION.whl + +If you prefer to install from sources, after installing the dependencies, obtain the source archive from +`GitHub `_. Once installed, ``cd`` into your ``diffpy.srxconfutils`` directory +and run the following :: + + pip install . + +This package also provides command-line utilities. To check the software has been installed correctly, type :: + + diffpy.srxconfutils --version + +You can also type the following command to verify the installation. :: + + python -c "import diffpy.srxconfutils; print(diffpy.srxconfutils.__version__)" + + +To view the basic usage and available commands, type :: + + diffpy.srxconfutils -h + + +Contact +------- + +For more information on diffpy.srxconfutils please visit the project `web-page `_ or email Simon J.L. Billinge group at sb2896@columbia.edu. + +Acknowledgements +---------------- + +``diffpy.srxconfutils`` is built and maintained with `scikit-package `_. From 2a9c9751baa306eb25ee16193075f97363446a04 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Fri, 3 Oct 2025 12:59:04 -0400 Subject: [PATCH 32/37] chore: add --file command mention to readme.rst --- README.rst | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 7baf7cc..3802fd2 100644 --- a/README.rst +++ b/README.rst @@ -57,8 +57,17 @@ To install this software from a Python wheel distribution format execute pip install ./diffpy.srxconfutils-VERSION.whl -If you prefer to install from sources, after installing the dependencies, obtain the source archive from -`GitHub `_. Once installed, ``cd`` into your ``diffpy.srxconfutils`` directory +If you are a developer, you can also install this package from sources. First, obtain the source archive +from `GitHub `_. +Install the packages in ``./requirements/conda.txt`` and ``./requirements/tests.txt`` +using the `--file`` command: + + conda activate conf-env + conda install --file ./requirements/conda.txt + conda install --file ./requirements/tests.txt + pip install -e . # assuming you are in the top level directory of the package + +After installing the dependencies, ``cd`` into your ``diffpy.srxconfutils`` directory and run the following :: pip install . From a02354ab7af957ea890d1ba10f1be7f2a259a22e Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Fri, 3 Oct 2025 12:59:54 -0400 Subject: [PATCH 33/37] chore: delete the 'longer descriptions' line --- README.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.rst b/README.rst index 3802fd2..9dfaf3c 100644 --- a/README.rst +++ b/README.rst @@ -25,8 +25,6 @@ Configuration utilities for diffpy project. Part of xPDFsuite. -* LONGER DESCRIPTION HERE - For more information about the diffpy.srxconfutils library, please consult our `online documentation `_. Citation From 02709547db4a8c05d55530805e1835c0506d5033 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Sun, 5 Oct 2025 19:09:22 -0400 Subject: [PATCH 34/37] chore: update readme file --- README.rst | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 9dfaf3c..8a852ff 100644 --- a/README.rst +++ b/README.rst @@ -32,10 +32,18 @@ Citation If you use diffpy.srxconfutils in a scientific publication, we would like you to cite this package as - diffpy.srxconfutils Package, https://github.com/diffpy/diffpy.srxconfutils + Xiaohao Yang, Pavol Juhas, Christopher L. Farrow and Simon J. L. Billinge, xPDFsuite: an end-to-end + software solution for high throughput pair distribution function transformation, visualization and + analysis, arXiv 1402.3163 (2014) Installation ------------ +``diffpy.srxconfutils`` is normally installed as part of the ``xpdfsuite`` software, so please refer to the +installation instructions detailed in the ``README.rst`` file of ``xpdfsuite`` `here `_. + +Independent Installation +------------------------ +You can also install ``diffpy.srxconfutils`` independently for yourself. Assuming you have a wheel file in the current working directory, in an active conda environment please type From 0ad1dce8c37a7e965fa4ef979ac78c82bf9320f0 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Tue, 7 Oct 2025 03:00:23 -0400 Subject: [PATCH 35/37] skpkg: add news file --- news/TEMPLATE.rst | 23 +++++++++++++++++++++++ news/skpkg.rst | 25 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 news/TEMPLATE.rst create mode 100644 news/skpkg.rst diff --git a/news/TEMPLATE.rst b/news/TEMPLATE.rst new file mode 100644 index 0000000..790d30b --- /dev/null +++ b/news/TEMPLATE.rst @@ -0,0 +1,23 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* + +**Security:** + +* diff --git a/news/skpkg.rst b/news/skpkg.rst new file mode 100644 index 0000000..b4d8767 --- /dev/null +++ b/news/skpkg.rst @@ -0,0 +1,25 @@ +**Added:** + +* + +**Changed:** + +* + +**Deprecated:** + +* + +**Removed:** + +* + +**Fixed:** + +* Support scikit-package Level 5 standard (https://scikit-package.github.io/scikit-package/). + +* Port legacy Python 2 code to support Python 3. + +**Security:** + +* From 35e524ea070f3fdd6c20be9e7226b1f101ab2b5e Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Tue, 7 Oct 2025 03:09:52 -0400 Subject: [PATCH 36/37] chore: update file level comments --- src/diffpy/srxconfutils/config.py | 8 ++++---- src/diffpy/srxconfutils/configtraits.py | 8 ++++---- src/diffpy/srxconfutils/tools.py | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/diffpy/srxconfutils/config.py b/src/diffpy/srxconfutils/config.py index 3a6c399..87970ad 100644 --- a/src/diffpy/srxconfutils/config.py +++ b/src/diffpy/srxconfutils/config.py @@ -1,14 +1,14 @@ #!/usr/bin/env python ############################################################################## # -# dpx.confutils by Simon J. L. Billinge group -# (c) 2013 Trustees of the Columbia University +# diffpy.srxconfutils by Simon J. L. Billinge group +# (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 LICENSENOTICE.txt for license information. +# See AUTHORS.rst for a list of people who contributed. +# See LICENSENOTICE.rst for license information. # ############################################################################## """Package for organizing program configurations. It can read/write diff --git a/src/diffpy/srxconfutils/configtraits.py b/src/diffpy/srxconfutils/configtraits.py index ae4c598..9e90e29 100644 --- a/src/diffpy/srxconfutils/configtraits.py +++ b/src/diffpy/srxconfutils/configtraits.py @@ -1,14 +1,14 @@ #!/usr/bin/env python ############################################################################## # -# dpx.confutils by Simon J. L. Billinge group -# (c) 2013 Trustees of the Columbia University +# diffpy.srxconfutils by Simon J. L. Billinge group +# (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 LICENSENOTICE.txt for license information. +# See AUTHORS.rst for a list of people who contributed. +# See LICENSENOTICE.rst for license information. # ############################################################################## """Package for organizing program configurations. It can read/write diff --git a/src/diffpy/srxconfutils/tools.py b/src/diffpy/srxconfutils/tools.py index 9c99e66..9b8a503 100644 --- a/src/diffpy/srxconfutils/tools.py +++ b/src/diffpy/srxconfutils/tools.py @@ -1,14 +1,14 @@ #!/usr/bin/env python ############################################################################## # -# dpx.confutils by Simon J. L. Billinge group -# (c) 2013 Trustees of the Columbia University +# diffpy.srxconfutils by Simon J. L. Billinge group +# (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 LICENSENOTICE.txt for license information. +# See AUTHORS.rst for a list of people who contributed. +# See LICENSENOTICE.rst for license information. # ############################################################################## From be82a2a9dbac40092a0c6af515bd3a4bfe505539 Mon Sep 17 00:00:00 2001 From: Zhi Ming Xu Date: Wed, 8 Oct 2025 01:35:18 -0400 Subject: [PATCH 37/37] chore: update build-wheel workflow file to match pdfgetx standards --- .../workflows/build-wheel-release-upload.yml | 163 ------------- .github/workflows/build-wheel-release.yml | 230 ++++++++++++++++++ 2 files changed, 230 insertions(+), 163 deletions(-) delete mode 100644 .github/workflows/build-wheel-release-upload.yml create mode 100644 .github/workflows/build-wheel-release.yml diff --git a/.github/workflows/build-wheel-release-upload.yml b/.github/workflows/build-wheel-release-upload.yml deleted file mode 100644 index 3e78d1d..0000000 --- a/.github/workflows/build-wheel-release-upload.yml +++ /dev/null @@ -1,163 +0,0 @@ -name: Release on GitHub - -on: - workflow_call: - secrets: - PAT_TOKEN: - description: "GitHub Personal Access Token" - required: true - -env: - TAG: ${{ github.ref_name }} - -defaults: - run: - shell: bash {0} - -jobs: - prepare-release: - if: ${{ ! contains(github.ref, 'rc') }} - runs-on: ubuntu-latest - steps: - - name: Checkout the repository - uses: actions/checkout@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - fetch-depth: 0 - ref: main - - - name: Update CHANGELOG - run: | - wget https://raw.githubusercontent.com/scikit-package/release-scripts/v0/.github/workflows/update-changelog.py - python update-changelog.py "$TAG" - rm update-changelog.py - - - name: Commit updated CHANGELOG.rst - run: | - git config user.name "github-actions[bot]" - git config user.email "github-actions[bot]@users.noreply.github.com" - git add . - if ! git diff --cached --quiet; then - git commit -m "update changelog for $TAG" - git push origin main - else - echo "No CHANGELOG.rst changes" - fi - - - name: New tag - run: | - git fetch --tags - git tag -d "$TAG" 2>/dev/null || true - git push origin ":$TAG" 2>/dev/null || true - git tag "$TAG" - git push origin "$TAG" - - - name: Get CHANGELOG.txt - run: | - wget https://raw.githubusercontent.com/scikit-package/release-scripts/v0/.github/workflows/get-latest-changelog.py - python get-latest-changelog.py "$TAG" - rm get-latest-changelog.py - - - name: Upload changelog.txt - uses: actions/upload-artifact@v4 - with: - name: changelog - path: CHANGELOG.txt - - release: - needs: [prepare-release] - if: always() - runs-on: ubuntu-latest - env: - REPO_FULL: ${{ github.repository }} - PAT_TOKEN: ${{ secrets.PAT_TOKEN }} - steps: - - name: Check prepare release - run: | - if [[ ${{ needs.prepare-release.result }} != 'success' && "$TAG" != *rc* ]]; then - echo "::error::Skipping release job because prepare-release failed" - exit 78 - fi - echo "Continuing with release job" - - - name: Download built wheels - uses: actions/download-artifact@v4 - with: - path: dist/ - - - name: Download changelog - if: ${{ needs.prepare-release.result == 'success' }} - uses: actions/download-artifact@v4 - with: - name: changelog - path: . - - - name: Download instructions - uses: actions/download-artifact@v4 - with: - name: instructions - path: . - - - name: Zip wheels and instructions into dist/srxconfutils-$TAG-wheels.zip - run: | - mkdir -p dist - find dist -type f -name '*.whl' | zip -j dist/srxconfutils-"$TAG"-wheels.zip -@ - zip -j dist/srxconfutils-"$TAG"-wheels.zip INSTRUCTIONS.txt - - - name: Prepare release metadata - id: meta - run: | - if [[ "$TAG" == *rc* ]]; then - PRERELEASE=true - TITLE="Pre-release $TAG" - BODY_RAW="Changelog: https://github.com/$REPO_FULL/commits/$TAG" - else - PRERELEASE=false - TITLE="Release $TAG" - BODY_RAW=$( payload.json - - echo "Release metadata:" - cat payload.json - - - name: Create GitHub Release - id: create_release - run: | - set -euo pipefail - - HTTP_STATUS=$( - curl --silent --output resp.json --write-out "%{http_code}" \ - -X POST "https://api.github.com/repos/$REPO_FULL/releases" \ - -H "Accept: application/vnd.github+json" \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer $PAT_TOKEN" \ - --data @payload.json - ) - if [[ "$HTTP_STATUS" -ne 201 ]]; then - echo "::error::Failed to create release (status $HTTP_STATUS)" - exit 1 - fi - - UPLOAD_URL=$(jq -r .upload_url resp.json | sed 's/{.*}//') - echo "upload_url=$UPLOAD_URL" >> $GITHUB_OUTPUT - - - name: Upload srxconfutils-$TAG-wheels.zip - if: steps.create_release.outputs.upload_url != '' - run: | - FILE=dist/srxconfutils-$TAG-wheels.zip - echo "Uploading asset: $FILE" - curl --silent --fail --data-binary @"$FILE" \ - -H "Content-Type: application/zip" \ - -H "Authorization: Bearer $PAT_TOKEN" \ - "${{ steps.create_release.outputs.upload_url }}?name=$(basename "$FILE")" diff --git a/.github/workflows/build-wheel-release.yml b/.github/workflows/build-wheel-release.yml new file mode 100644 index 0000000..8409857 --- /dev/null +++ b/.github/workflows/build-wheel-release.yml @@ -0,0 +1,230 @@ +name: Build Wheels and Release + +on: + workflow_dispatch: + push: + tags: + - "*" + +env: + PYTHON_VERSIONS: '["3.11","3.12","3.13"]' + +permissions: + contents: write + actions: read + packages: write + +concurrency: + group: build-wheels-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash {0} + +jobs: + get-python-versions: + runs-on: ubuntu-latest + outputs: + py_ver: ${{ steps.set.outputs.py_ver }} + py_last: ${{ steps.set.outputs.py_last }} + steps: + - id: set + run: | + echo py_ver=$PYTHON_VERSIONS >> $GITHUB_OUTPUT + echo "py_last=${{ fromJson(env.PYTHON_VERSIONS)[0] }}" >> $GITHUB_OUTPUT + + check-tag-on-main: + runs-on: ubuntu-latest + steps: + - name: Checkout repository with full history + uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT_TOKEN }} + fetch-depth: 0 + + - name: Verify tag + run: | + git fetch origin main + TAG_COMMIT=$(git rev-parse ${{ github.ref_name }}) + if git merge-base --is-ancestor "$TAG_COMMIT" origin/main; then + echo "Tag ${{ github.ref_name }} ($TAG_COMMIT) is contained in main" + else + echo "::error::Tag ${{ github.ref_name }} ($TAG_COMMIT) is not found in main. Please release from the main branch." + exit 1 + fi + + check-tag-privilege: + # No third party actions used + uses: scikit-package/release-scripts/.github/workflows/_check-tag-privilege.yml@v0 + with: + maintainer_github_username: sbillinge + + build-sdist: + needs: [check-tag-privilege, get-python-versions, check-tag-on-main] + runs-on: ubuntu-latest + + steps: + - name: Checkout + # GitHub officially-maintained actions + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Python + # GitHub officially-maintained actions + uses: actions/setup-python@v5 + with: + python-version: ${{ needs.get-python-versions.outputs.py_last }} + + - name: Build docs and remove fonts that blow up the wheel size + run: | + python -m pip install Sphinx sphinx-rtd-theme sphinx-copybutton m2r + cd docs + make html + cd build/html/_static/css/fonts + find . -type f ! -name '*.ttf' -delete + cd ../.. + rm -rf fonts + cd .. + rm -rf _sources + cd .. + rm -rf doctrees + cd ../.. + + - name: Make sdist + run: | + python -m pip install --upgrade pip build cython setuptools setuptools-git-versioning + python -m build --sdist --no-isolation + + - name: Strip source codes + run: | + set -euo pipefail + tar xf dist/diffpy_srxconfutils-*.tar.gz + SRC_DIR=$(find . -maxdepth 1 -type d -name 'diffpy_srxconfutils-*' | head -n1) + find "$SRC_DIR" -type f -name '*.c' -print0 \ + | xargs -0 perl -i.bak -0777 -pe 's{/\*.*?\*/}{}gs' + find "$SRC_DIR" -type f -name '*.c.bak' -delete + tar czf dist/"${SRC_DIR#./}".tar.gz "$SRC_DIR" + rm -rf "$SRC_DIR" + + - name: Upload sdist + # GitHub officially-maintained actions + uses: actions/upload-artifact@v4 + with: + name: sdist + path: dist/ + retention-days: 1 + + - name: Upload INSTRUCTIONS.txt + uses: actions/upload-artifact@v4 + with: + name: instructions + path: INSTRUCTIONS.txt + + build-wheels: + needs: [build-sdist, get-python-versions] + + name: Build wheels ${{ matrix.python }}-${{ matrix.buildplat }} + runs-on: ${{ matrix.buildplat }} + strategy: + fail-fast: false + matrix: + buildplat: + - ubuntu-latest + - macos-14 + - windows-latest + python: ${{ fromJSON(needs.get-python-versions.outputs.py_ver) }} + steps: + - name: Download sdist + # GitHub officially-maintained actions + uses: actions/download-artifact@v4 + with: + name: sdist + path: dist/ + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Build wheels + run: | + # setuptools-git-versioning only look into .git and PKG-INFO in the top directory + which python; python --version + which pip; pip --version + python -m pip install --upgrade pip build cython setuptools setuptools-git-versioning + tar xf dist/diffpy_srxconfutils-*.tar.gz + cd diffpy_srxconfutils-* + python -m pip wheel . --no-deps --no-build-isolation --wheel-dir ./../dist + + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.python }}-${{ matrix.buildplat }} + path: dist/*.whl + + test-wheels: + needs: [build-wheels, get-python-versions] + + name: Test wheels ${{ matrix.python }}-${{ matrix.buildplat }} + runs-on: ${{ matrix.buildplat }} + strategy: + fail-fast: false + matrix: + buildplat: + - ubuntu-latest + - macos-14 + - windows-latest + python: ${{ fromJson(needs.get-python-versions.outputs.py_ver) }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Download wheels + uses: actions/download-artifact@v4 + with: + name: wheels-${{ matrix.python }}-${{ matrix.buildplat }} + path: dist/ + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python }} + + - name: Install wheels + run: | + python -m pip install --upgrade pip pytest setuptools-git-versioning + python -m pip install dist/*.whl + + - name: Assert no source files in installed package + run: | + # For debugging + # touch $(python -c "import sysconfig, os; print(os.path.join(sysconfig.get_paths()['purelib'], 'diffpy/srxconfutils', 'fake.py'))") + python - << 'EOF' + import os, glob, sys, sysconfig + sp = sysconfig.get_paths()['purelib'] + pkg = os.path.join(sp, 'diffpy', 'srxconfutils') + patterns = [os.path.join(pkg, '*.py'), + os.path.join(pkg, '*.c')] + bad = [] + for p in patterns: + bad.extend(glob.glob(p)) + + if bad: + print("Found leftover source files in installed package:") + for f in bad: + print(" -", f) + sys.exit(1) + + print("No .py or .c files present in diffpy/srxconfutils/") + EOF + + - name: Run tests + run: python -m pytest + + release: + needs: [test-wheels] + uses: ./.github/workflows/release-github.yml + secrets: + PAT_TOKEN: ${{ secrets.PAT_TOKEN }}