Modernize packaging, make ELSEPA configurable, add basedpyright #1
Loading…
Reference in a new issue
No description provided.
Delete branch "modernize-packaging-and-fixes"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Eight commits, grouped so the whitespace churn stays out of the code changes.
Packaging
setup.pyusedfrom distutils.core import setup. distutils was removed from the stdlib in Python 3.12 (PEP 632), so it only worked via the setuptools shim. Replaced withpyproject.toml: dynamic version fromcstool.__version__,requires-python = ">=3.12", SPDX license, andsetuptools+importlib_resourcesdropped from runtime dependencies (the latter was declared but never imported).ELSEPA configuration
The ELSEPA distribution directory was only configurable by putting it on
PATH, which is the wrong mechanism — ELSEPA reads itsz_nnn.dendatabase from the directory holding the binary, so it is a data directory, not a binary search path. Now resolved via--elsepa-dir, then$ELSEPA_DIR, thenPATH, with validation up front.This also fixes error reporting:
shutil.which()returnsNonerather than raising, so the oldtry/exceptnever fired and the code silently copied the entire working directory to a temp dir before failing with a confusingFileNotFoundError.Bug fixes found by basedpyright
NameErrors inashley.py(L_ashley_wo_exvsL_Ashley_wo_ex) on functions exported fromdielectric_function/__init__.py— public API that raised on first call.dimfp_table.IMFP_ICDFreferenced an undefinedP_omega.parse_photoat/parse_atomic_relax/parse_electronsreturnedNonefor an unknown element, surfacing asAttributeError: 'NoneType' object has no attribute 'reactions'several frames later.endf_readerhit a bareValueErroron EOF.band_structure's "Unknown band structure model {}" was never.format()ed.optical.py's bareexcept:reported every failure as "No optical data provided".Also fixed the
..parent traversal inimportlib.resources, which is not supported by the Traversable protocol and would break on a zipped install.basedpyright
Configured at
typeCheckingMode = "standard"with eight rules disabled, each verified to fire only on pint and numba stub deficiencies rather than on project code. Reports 0 errors, 0 warnings.Verification
uv buildsucceeds, basedpyright is clean, and a fullcstool ./silicon.yamlcompile runs to completion (exit 0) withELSEPA_DIRset.Not addressed
interpolate.py:17still emitsUserWarning: 'where' used without 'out', expect uninitialized memory in outputtwice per run.obtain_endf_filesstill downloads into the installed package directory; a user cache directory would be the durable fix.band_structure.__init__validatesmodelafter loading fields, so an unknown model with missing fields still dies withKeyErrorfirst.pkg_resources is removed in setuptools 81+, so the ENDF resource lookup had to move to importlib.resources. Two corrections to that migration: - files(__name__).joinpath('../data/...') relied on the Traversable being a real pathlib.Path so the OS could resolve the '..'. The Traversable protocol does not support parent traversal, so this breaks for a zipped install, where files() returns a zipfile.Path and '..' is looked up as a literal entry. Addressed by anchoring at files('cstool') instead. - download_file swallowed the underlying network error; it now chains it with 'raise ... from e'. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>setup.py used `from distutils.core import setup`. distutils was removed from the stdlib in Python 3.12 (PEP 632), so this only worked because setuptools injects a shim via distutils-precedence.pth: $ python3.14 -c "import distutils; print(distutils.__file__)" /usr/lib/python3.14/site-packages/setuptools/_distutils/__init__.py That shim is also why setuptools was listed as a runtime dependency. With a proper [build-system] table it goes back to being a build dependency. Other changes: - Version is now dynamic from cstool.__version__, instead of being duplicated in setup.py and cstool/__init__.py. - importlib_resources dropped: the backport was declared but never imported; the code uses the stdlib importlib.resources. - requires-python = ">=3.12", which is what the code actually needs. cstool.endf.obtain_endf_files calls importlib.resources.as_file() on a directory, and directory support landed in 3.12 (see the _temp_dir / _temp_file dispatch in importlib/resources/_common.py). - license declared as an SPDX expression per PEP 639. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>run_elscata located ELSEPA by taking dirname(shutil.which('elscata')), which meant the only way to configure it was to put the ELSEPA distribution directory on PATH. PATH is the wrong mechanism here. ELSEPA reads its z_nnn.den and z_nnn.dfs atomic database from the directory holding the binary ("These files must be placed in the same directory as the executable binary file." - ELSEPA readme.txt), which is why run_elscata copies the whole directory rather than just the binary. So the setting is really a data directory, and the obvious tidy-up of symlinking elscata into ~/.local/bin silently breaks: the copy then contains no database. find_elsepa_dir() now resolves, in order: an explicit argument, the ELSEPA_DIR environment variable, then elscata on PATH. It validates that the directory exists, holds elscata, and holds the z_nnn.den files. mott_dimfp and run_elscata_parallel take an elsepa_dir argument, and cstool grows a --elsepa-dir flag. Resolution happens once in run_elscata_parallel so a bad configuration produces one clear error rather than one per thread-pool worker. This also fixes the error reporting. shutil.which() returns None rather than raising, so the previous try/except never fired; str(None) became the literal "None", Path("None").parent became ".", and "." exists, so the code silently copied the entire current working directory to a temp dir before failing with a confusing FileNotFoundError on the missing binary. Also drops apps/cstool.py's use of cst.input_data.param_file, which only resolved because an unrelated top-level import bound input_data as an attribute of the cstool package. param_file was already imported directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>Found by basedpyright (added in the next commit). Three NameErrors: - ashley.py called L_ashley_wo_ex and L_ashley_w_ex, but the functions are defined as L_Ashley_wo_ex and L_Ashley_w_ex. Both dimfp_ashley and dimfp_ashley_exchange are exported from dielectric_function/__init__.py, so this was public API that raised on first call. - dimfp_table.IMFP_ICDF used len(P_omega), which does not exist; the parameter is P. The method has no callers, so it was never reached. Three silent failures: - parse_photoat, parse_atomic_relax and parse_electrons fell off the end returning None when the archive held no file for that element, so an unsupported Z surfaced several frames later as "AttributeError: 'NoneType' object has no attribute 'reactions'". They now raise, e.g. "No electron ionization data for Z=999 in .../electrons.zip". The three near-identical bodies share a helper. - endf_reader called int(line[70:72]) on the empty string returned at EOF, giving a bare ValueError. It now reports reaching end of file while looking for the first header. - band_structure's "Unknown band structure model {}" was never .format()ed, so it printed a literal {}. optical.py's bare `except:` reported every failure as "No optical data provided"; it now checks for the missing key explicitly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>basedpyright defaults to typeCheckingMode = "recommended", which on this codebase reports 137 errors and 2616 warnings. Almost all of it is the "nothing is annotated" family: reportUnknownMemberType (625), reportUnknownArgumentType (592), reportUnknownVariableType (426), reportMissingParameterType (291), reportAny (108). "standard" drops that family and leaves 76 diagnostics. Of those, eight rules are disabled here, each verified to fire only on third-party stub deficiencies rather than on our code: - pint (reportIndexIssue, reportOperatorIssue, reportOptionalOperand, reportAttributeAccessIssue) types units(...) as a union including Unit, and PlainQuantity declares neither __getitem__ nor __setitem__, so `np.zeros(n) * units('nm^-1')` followed by `imfp[i] = x` looks illegal despite working. - numba (reportPrivateImportUsage, reportFunctionMemberAccess, reportCallIssue, reportArgumentType) does not re-export types/carray in its stubs and attaches .ctypes to jitted functions at runtime. The remaining rules stay enabled; the diagnostics they reported were real and are fixed in the preceding commit. Result is 0 errors, 0 warnings. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>