mlmm-toolkit 0.2.2.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hessian_ff/__init__.py +50 -0
- hessian_ff/analytical_hessian.py +609 -0
- hessian_ff/constants.py +46 -0
- hessian_ff/forcefield.py +339 -0
- hessian_ff/loaders.py +608 -0
- hessian_ff/native/Makefile +8 -0
- hessian_ff/native/__init__.py +28 -0
- hessian_ff/native/analytical_hessian.py +88 -0
- hessian_ff/native/analytical_hessian_ext.cpp +258 -0
- hessian_ff/native/bonded.py +82 -0
- hessian_ff/native/bonded_ext.cpp +640 -0
- hessian_ff/native/loader.py +349 -0
- hessian_ff/native/nonbonded.py +118 -0
- hessian_ff/native/nonbonded_ext.cpp +1150 -0
- hessian_ff/prmtop_parmed.py +23 -0
- hessian_ff/system.py +107 -0
- hessian_ff/terms/__init__.py +14 -0
- hessian_ff/terms/angle.py +73 -0
- hessian_ff/terms/bond.py +44 -0
- hessian_ff/terms/cmap.py +406 -0
- hessian_ff/terms/dihedral.py +141 -0
- hessian_ff/terms/nonbonded.py +209 -0
- hessian_ff/tests/__init__.py +0 -0
- hessian_ff/tests/conftest.py +75 -0
- hessian_ff/tests/data/small/complex.parm7 +1346 -0
- hessian_ff/tests/data/small/complex.pdb +125 -0
- hessian_ff/tests/data/small/complex.rst7 +63 -0
- hessian_ff/tests/test_coords_input.py +44 -0
- hessian_ff/tests/test_energy_force.py +49 -0
- hessian_ff/tests/test_hessian.py +137 -0
- hessian_ff/tests/test_smoke.py +18 -0
- hessian_ff/tests/test_validation.py +40 -0
- hessian_ff/workflows.py +889 -0
- mlmm/__init__.py +36 -0
- mlmm/__main__.py +7 -0
- mlmm/_version.py +34 -0
- mlmm/add_elem_info.py +374 -0
- mlmm/advanced_help.py +91 -0
- mlmm/align_freeze_atoms.py +601 -0
- mlmm/all.py +3535 -0
- mlmm/bond_changes.py +231 -0
- mlmm/bool_compat.py +223 -0
- mlmm/cli.py +574 -0
- mlmm/cli_utils.py +166 -0
- mlmm/default_group.py +337 -0
- mlmm/defaults.py +467 -0
- mlmm/define_layer.py +526 -0
- mlmm/dft.py +1041 -0
- mlmm/energy_diagram.py +253 -0
- mlmm/extract.py +2213 -0
- mlmm/fix_altloc.py +464 -0
- mlmm/freq.py +1406 -0
- mlmm/harmonic_constraints.py +140 -0
- mlmm/hessian_cache.py +44 -0
- mlmm/hessian_calc.py +174 -0
- mlmm/irc.py +638 -0
- mlmm/mlmm_calc.py +2262 -0
- mlmm/mm_parm.py +945 -0
- mlmm/oniom_export.py +1983 -0
- mlmm/oniom_import.py +457 -0
- mlmm/opt.py +1742 -0
- mlmm/path_opt.py +1353 -0
- mlmm/path_search.py +2299 -0
- mlmm/preflight.py +88 -0
- mlmm/py.typed +1 -0
- mlmm/pysis_runner.py +45 -0
- mlmm/scan.py +1047 -0
- mlmm/scan2d.py +1226 -0
- mlmm/scan3d.py +1265 -0
- mlmm/scan_common.py +184 -0
- mlmm/summary_log.py +736 -0
- mlmm/trj2fig.py +448 -0
- mlmm/tsopt.py +2871 -0
- mlmm/utils.py +2309 -0
- mlmm/xtb_embedcharge_correction.py +475 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/METADATA +1159 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/RECORD +372 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/WHEEL +5 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/entry_points.txt +2 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/licenses/LICENSE +674 -0
- mlmm_toolkit-0.2.2.dev0.dist-info/top_level.txt +4 -0
- pysisyphus/Geometry.py +1667 -0
- pysisyphus/LICENSE +674 -0
- pysisyphus/TableFormatter.py +63 -0
- pysisyphus/TablePrinter.py +74 -0
- pysisyphus/__init__.py +12 -0
- pysisyphus/calculators/AFIR.py +452 -0
- pysisyphus/calculators/AnaPot.py +20 -0
- pysisyphus/calculators/AnaPot2.py +48 -0
- pysisyphus/calculators/AnaPot3.py +12 -0
- pysisyphus/calculators/AnaPot4.py +20 -0
- pysisyphus/calculators/AnaPotBase.py +337 -0
- pysisyphus/calculators/AnaPotCBM.py +25 -0
- pysisyphus/calculators/AtomAtomTransTorque.py +154 -0
- pysisyphus/calculators/CFOUR.py +250 -0
- pysisyphus/calculators/Calculator.py +844 -0
- pysisyphus/calculators/CerjanMiller.py +24 -0
- pysisyphus/calculators/Composite.py +123 -0
- pysisyphus/calculators/ConicalIntersection.py +171 -0
- pysisyphus/calculators/DFTBp.py +430 -0
- pysisyphus/calculators/DFTD3.py +66 -0
- pysisyphus/calculators/DFTD4.py +84 -0
- pysisyphus/calculators/Dalton.py +61 -0
- pysisyphus/calculators/Dimer.py +681 -0
- pysisyphus/calculators/Dummy.py +20 -0
- pysisyphus/calculators/EGO.py +76 -0
- pysisyphus/calculators/EnergyMin.py +224 -0
- pysisyphus/calculators/ExternalPotential.py +264 -0
- pysisyphus/calculators/FakeASE.py +35 -0
- pysisyphus/calculators/FourWellAnaPot.py +28 -0
- pysisyphus/calculators/FreeEndNEBPot.py +39 -0
- pysisyphus/calculators/Gaussian09.py +18 -0
- pysisyphus/calculators/Gaussian16.py +726 -0
- pysisyphus/calculators/HardSphere.py +159 -0
- pysisyphus/calculators/IDPPCalculator.py +49 -0
- pysisyphus/calculators/IPIClient.py +133 -0
- pysisyphus/calculators/IPIServer.py +234 -0
- pysisyphus/calculators/LEPSBase.py +24 -0
- pysisyphus/calculators/LEPSExpr.py +139 -0
- pysisyphus/calculators/LennardJones.py +80 -0
- pysisyphus/calculators/MOPAC.py +219 -0
- pysisyphus/calculators/MullerBrownSympyPot.py +51 -0
- pysisyphus/calculators/MultiCalc.py +85 -0
- pysisyphus/calculators/NFK.py +45 -0
- pysisyphus/calculators/OBabel.py +87 -0
- pysisyphus/calculators/ONIOMv2.py +1129 -0
- pysisyphus/calculators/ORCA.py +893 -0
- pysisyphus/calculators/ORCA5.py +6 -0
- pysisyphus/calculators/OpenMM.py +88 -0
- pysisyphus/calculators/OpenMolcas.py +281 -0
- pysisyphus/calculators/OverlapCalculator.py +908 -0
- pysisyphus/calculators/Psi4.py +218 -0
- pysisyphus/calculators/PyPsi4.py +37 -0
- pysisyphus/calculators/PySCF.py +341 -0
- pysisyphus/calculators/PyXTB.py +73 -0
- pysisyphus/calculators/QCEngine.py +106 -0
- pysisyphus/calculators/Rastrigin.py +22 -0
- pysisyphus/calculators/Remote.py +76 -0
- pysisyphus/calculators/Rosenbrock.py +15 -0
- pysisyphus/calculators/SocketCalc.py +97 -0
- pysisyphus/calculators/TIP3P.py +111 -0
- pysisyphus/calculators/TransTorque.py +161 -0
- pysisyphus/calculators/Turbomole.py +965 -0
- pysisyphus/calculators/VRIPot.py +37 -0
- pysisyphus/calculators/WFOWrapper.py +333 -0
- pysisyphus/calculators/WFOWrapper2.py +341 -0
- pysisyphus/calculators/XTB.py +418 -0
- pysisyphus/calculators/__init__.py +81 -0
- pysisyphus/calculators/cosmo_data.py +139 -0
- pysisyphus/calculators/parser.py +150 -0
- pysisyphus/color.py +19 -0
- pysisyphus/config.py +133 -0
- pysisyphus/constants.py +65 -0
- pysisyphus/cos/AdaptiveNEB.py +230 -0
- pysisyphus/cos/ChainOfStates.py +725 -0
- pysisyphus/cos/FreeEndNEB.py +25 -0
- pysisyphus/cos/FreezingString.py +103 -0
- pysisyphus/cos/GrowingChainOfStates.py +71 -0
- pysisyphus/cos/GrowingNT.py +309 -0
- pysisyphus/cos/GrowingString.py +508 -0
- pysisyphus/cos/NEB.py +189 -0
- pysisyphus/cos/SimpleZTS.py +64 -0
- pysisyphus/cos/__init__.py +22 -0
- pysisyphus/cos/stiffness.py +199 -0
- pysisyphus/drivers/__init__.py +17 -0
- pysisyphus/drivers/afir.py +855 -0
- pysisyphus/drivers/barriers.py +271 -0
- pysisyphus/drivers/birkholz.py +138 -0
- pysisyphus/drivers/cluster.py +318 -0
- pysisyphus/drivers/diabatization.py +133 -0
- pysisyphus/drivers/merge.py +368 -0
- pysisyphus/drivers/merge_mol2.py +322 -0
- pysisyphus/drivers/opt.py +375 -0
- pysisyphus/drivers/perf.py +91 -0
- pysisyphus/drivers/pka.py +52 -0
- pysisyphus/drivers/precon_pos_rot.py +669 -0
- pysisyphus/drivers/rates.py +480 -0
- pysisyphus/drivers/replace.py +219 -0
- pysisyphus/drivers/scan.py +212 -0
- pysisyphus/drivers/spectrum.py +166 -0
- pysisyphus/drivers/thermo.py +31 -0
- pysisyphus/dynamics/Gaussian.py +103 -0
- pysisyphus/dynamics/__init__.py +20 -0
- pysisyphus/dynamics/colvars.py +136 -0
- pysisyphus/dynamics/driver.py +297 -0
- pysisyphus/dynamics/helpers.py +256 -0
- pysisyphus/dynamics/lincs.py +105 -0
- pysisyphus/dynamics/mdp.py +364 -0
- pysisyphus/dynamics/rattle.py +121 -0
- pysisyphus/dynamics/thermostats.py +128 -0
- pysisyphus/dynamics/wigner.py +266 -0
- pysisyphus/elem_data.py +3473 -0
- pysisyphus/exceptions.py +2 -0
- pysisyphus/filtertrj.py +69 -0
- pysisyphus/helpers.py +623 -0
- pysisyphus/helpers_pure.py +649 -0
- pysisyphus/init_logging.py +50 -0
- pysisyphus/intcoords/Bend.py +69 -0
- pysisyphus/intcoords/Bend2.py +25 -0
- pysisyphus/intcoords/BondedFragment.py +32 -0
- pysisyphus/intcoords/Cartesian.py +41 -0
- pysisyphus/intcoords/CartesianCoords.py +140 -0
- pysisyphus/intcoords/Coords.py +56 -0
- pysisyphus/intcoords/DLC.py +197 -0
- pysisyphus/intcoords/DistanceFunction.py +34 -0
- pysisyphus/intcoords/DummyImproper.py +70 -0
- pysisyphus/intcoords/DummyTorsion.py +72 -0
- pysisyphus/intcoords/LinearBend.py +105 -0
- pysisyphus/intcoords/LinearDisplacement.py +80 -0
- pysisyphus/intcoords/OutOfPlane.py +59 -0
- pysisyphus/intcoords/PrimTypes.py +286 -0
- pysisyphus/intcoords/Primitive.py +137 -0
- pysisyphus/intcoords/RedundantCoords.py +659 -0
- pysisyphus/intcoords/RobustTorsion.py +59 -0
- pysisyphus/intcoords/Rotation.py +147 -0
- pysisyphus/intcoords/Stretch.py +31 -0
- pysisyphus/intcoords/Torsion.py +101 -0
- pysisyphus/intcoords/Torsion2.py +25 -0
- pysisyphus/intcoords/Translation.py +45 -0
- pysisyphus/intcoords/__init__.py +61 -0
- pysisyphus/intcoords/augment_bonds.py +126 -0
- pysisyphus/intcoords/derivatives.py +10512 -0
- pysisyphus/intcoords/eval.py +80 -0
- pysisyphus/intcoords/exceptions.py +37 -0
- pysisyphus/intcoords/findiffs.py +48 -0
- pysisyphus/intcoords/generate_derivatives.py +414 -0
- pysisyphus/intcoords/helpers.py +235 -0
- pysisyphus/intcoords/logging_conf.py +10 -0
- pysisyphus/intcoords/mp_derivatives.py +10836 -0
- pysisyphus/intcoords/setup.py +962 -0
- pysisyphus/intcoords/setup_fast.py +176 -0
- pysisyphus/intcoords/update.py +272 -0
- pysisyphus/intcoords/valid.py +89 -0
- pysisyphus/interpolate/Geodesic.py +93 -0
- pysisyphus/interpolate/IDPP.py +55 -0
- pysisyphus/interpolate/Interpolator.py +116 -0
- pysisyphus/interpolate/LST.py +70 -0
- pysisyphus/interpolate/Redund.py +152 -0
- pysisyphus/interpolate/__init__.py +9 -0
- pysisyphus/interpolate/helpers.py +34 -0
- pysisyphus/io/__init__.py +22 -0
- pysisyphus/io/aomix.py +178 -0
- pysisyphus/io/cjson.py +24 -0
- pysisyphus/io/crd.py +101 -0
- pysisyphus/io/cube.py +220 -0
- pysisyphus/io/fchk.py +184 -0
- pysisyphus/io/hdf5.py +49 -0
- pysisyphus/io/hessian.py +72 -0
- pysisyphus/io/mol2.py +146 -0
- pysisyphus/io/molden.py +293 -0
- pysisyphus/io/orca.py +189 -0
- pysisyphus/io/pdb.py +269 -0
- pysisyphus/io/psf.py +79 -0
- pysisyphus/io/pubchem.py +31 -0
- pysisyphus/io/qcschema.py +34 -0
- pysisyphus/io/sdf.py +29 -0
- pysisyphus/io/xyz.py +61 -0
- pysisyphus/io/zmat.py +175 -0
- pysisyphus/irc/DWI.py +108 -0
- pysisyphus/irc/DampedVelocityVerlet.py +134 -0
- pysisyphus/irc/Euler.py +22 -0
- pysisyphus/irc/EulerPC.py +345 -0
- pysisyphus/irc/GonzalezSchlegel.py +187 -0
- pysisyphus/irc/IMKMod.py +164 -0
- pysisyphus/irc/IRC.py +878 -0
- pysisyphus/irc/IRCDummy.py +10 -0
- pysisyphus/irc/Instanton.py +307 -0
- pysisyphus/irc/LQA.py +53 -0
- pysisyphus/irc/ModeKill.py +136 -0
- pysisyphus/irc/ParamPlot.py +53 -0
- pysisyphus/irc/RK4.py +36 -0
- pysisyphus/irc/__init__.py +31 -0
- pysisyphus/irc/initial_displ.py +219 -0
- pysisyphus/linalg.py +411 -0
- pysisyphus/line_searches/Backtracking.py +88 -0
- pysisyphus/line_searches/HagerZhang.py +184 -0
- pysisyphus/line_searches/LineSearch.py +232 -0
- pysisyphus/line_searches/StrongWolfe.py +108 -0
- pysisyphus/line_searches/__init__.py +9 -0
- pysisyphus/line_searches/interpol.py +15 -0
- pysisyphus/modefollow/NormalMode.py +40 -0
- pysisyphus/modefollow/__init__.py +10 -0
- pysisyphus/modefollow/davidson.py +199 -0
- pysisyphus/modefollow/lanczos.py +95 -0
- pysisyphus/optimizers/BFGS.py +99 -0
- pysisyphus/optimizers/BacktrackingOptimizer.py +113 -0
- pysisyphus/optimizers/ConjugateGradient.py +98 -0
- pysisyphus/optimizers/CubicNewton.py +75 -0
- pysisyphus/optimizers/FIRE.py +113 -0
- pysisyphus/optimizers/HessianOptimizer.py +1176 -0
- pysisyphus/optimizers/LBFGS.py +228 -0
- pysisyphus/optimizers/LayerOpt.py +411 -0
- pysisyphus/optimizers/MicroOptimizer.py +169 -0
- pysisyphus/optimizers/NCOptimizer.py +90 -0
- pysisyphus/optimizers/Optimizer.py +1084 -0
- pysisyphus/optimizers/PreconLBFGS.py +260 -0
- pysisyphus/optimizers/PreconSteepestDescent.py +7 -0
- pysisyphus/optimizers/QuickMin.py +74 -0
- pysisyphus/optimizers/RFOptimizer.py +181 -0
- pysisyphus/optimizers/RSA.py +99 -0
- pysisyphus/optimizers/StabilizedQNMethod.py +248 -0
- pysisyphus/optimizers/SteepestDescent.py +23 -0
- pysisyphus/optimizers/StringOptimizer.py +173 -0
- pysisyphus/optimizers/__init__.py +41 -0
- pysisyphus/optimizers/closures.py +301 -0
- pysisyphus/optimizers/cls_map.py +58 -0
- pysisyphus/optimizers/exceptions.py +6 -0
- pysisyphus/optimizers/gdiis.py +280 -0
- pysisyphus/optimizers/guess_hessians.py +311 -0
- pysisyphus/optimizers/hessian_updates.py +355 -0
- pysisyphus/optimizers/poly_fit.py +285 -0
- pysisyphus/optimizers/precon.py +153 -0
- pysisyphus/optimizers/restrict_step.py +24 -0
- pysisyphus/pack.py +172 -0
- pysisyphus/peakdetect.py +948 -0
- pysisyphus/plot.py +1031 -0
- pysisyphus/run.py +2106 -0
- pysisyphus/socket_helper.py +74 -0
- pysisyphus/stocastic/FragmentKick.py +132 -0
- pysisyphus/stocastic/Kick.py +81 -0
- pysisyphus/stocastic/Pipeline.py +303 -0
- pysisyphus/stocastic/__init__.py +21 -0
- pysisyphus/stocastic/align.py +127 -0
- pysisyphus/testing.py +96 -0
- pysisyphus/thermo.py +156 -0
- pysisyphus/trj.py +824 -0
- pysisyphus/tsoptimizers/RSIRFOptimizer.py +56 -0
- pysisyphus/tsoptimizers/RSPRFOptimizer.py +182 -0
- pysisyphus/tsoptimizers/TRIM.py +59 -0
- pysisyphus/tsoptimizers/TSHessianOptimizer.py +463 -0
- pysisyphus/tsoptimizers/__init__.py +23 -0
- pysisyphus/wavefunction/Basis.py +239 -0
- pysisyphus/wavefunction/DIIS.py +76 -0
- pysisyphus/wavefunction/__init__.py +25 -0
- pysisyphus/wavefunction/build_ext.py +42 -0
- pysisyphus/wavefunction/cart2sph.py +190 -0
- pysisyphus/wavefunction/diabatization.py +304 -0
- pysisyphus/wavefunction/excited_states.py +435 -0
- pysisyphus/wavefunction/gen_ints.py +1811 -0
- pysisyphus/wavefunction/helpers.py +104 -0
- pysisyphus/wavefunction/ints/__init__.py +0 -0
- pysisyphus/wavefunction/ints/boys.py +193 -0
- pysisyphus/wavefunction/ints/boys_table_N_64_xasym_27.1_step_0.01.npy +0 -0
- pysisyphus/wavefunction/ints/cart_gto3d.py +176 -0
- pysisyphus/wavefunction/ints/coulomb3d.py +25928 -0
- pysisyphus/wavefunction/ints/diag_quadrupole3d.py +10036 -0
- pysisyphus/wavefunction/ints/dipole3d.py +8762 -0
- pysisyphus/wavefunction/ints/int2c2e3d.py +7198 -0
- pysisyphus/wavefunction/ints/int3c2e3d_sph.py +65040 -0
- pysisyphus/wavefunction/ints/kinetic3d.py +8240 -0
- pysisyphus/wavefunction/ints/ovlp3d.py +3777 -0
- pysisyphus/wavefunction/ints/quadrupole3d.py +15054 -0
- pysisyphus/wavefunction/ints/self_ovlp3d.py +198 -0
- pysisyphus/wavefunction/localization.py +458 -0
- pysisyphus/wavefunction/multipole.py +159 -0
- pysisyphus/wavefunction/normalization.py +36 -0
- pysisyphus/wavefunction/pop_analysis.py +134 -0
- pysisyphus/wavefunction/shells.py +1171 -0
- pysisyphus/wavefunction/wavefunction.py +504 -0
- pysisyphus/wrapper/__init__.py +11 -0
- pysisyphus/wrapper/exceptions.py +2 -0
- pysisyphus/wrapper/jmol.py +120 -0
- pysisyphus/wrapper/mwfn.py +169 -0
- pysisyphus/wrapper/packmol.py +71 -0
- pysisyphus/xyzloader.py +168 -0
- pysisyphus/yaml_mods.py +45 -0
- thermoanalysis/LICENSE +674 -0
- thermoanalysis/QCData.py +244 -0
- thermoanalysis/__init__.py +0 -0
- thermoanalysis/config.py +3 -0
- thermoanalysis/constants.py +20 -0
- thermoanalysis/thermo.py +1011 -0
mlmm/cli_utils.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""CLI utilities for standardized exception handling and shared boilerplate."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import gc
|
|
7
|
+
import os
|
|
8
|
+
import shutil
|
|
9
|
+
import sys
|
|
10
|
+
import textwrap
|
|
11
|
+
import traceback
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any, Callable, Dict, Optional, Tuple, Type
|
|
14
|
+
|
|
15
|
+
import click
|
|
16
|
+
|
|
17
|
+
from .utils import deep_update, load_yaml_dict
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Click parameter source helper
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
def make_is_param_explicit(ctx: "click.Context"):
|
|
25
|
+
"""Return a helper that checks whether a Click parameter was set explicitly."""
|
|
26
|
+
from click.core import ParameterSource
|
|
27
|
+
def _is_param_explicit(name: str) -> bool:
|
|
28
|
+
try:
|
|
29
|
+
source = ctx.get_parameter_source(name)
|
|
30
|
+
return source not in (None, ParameterSource.DEFAULT)
|
|
31
|
+
except Exception:
|
|
32
|
+
return False
|
|
33
|
+
return _is_param_explicit
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
_TRUE_VALUES = {"true", "1", "yes", "y", "t"}
|
|
37
|
+
_FALSE_VALUES = {"false", "0", "no", "n", "f"}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def parse_bool(value: Any) -> bool:
|
|
41
|
+
"""Parse common boolean strings into bool; raise ValueError on invalid input."""
|
|
42
|
+
if value is None:
|
|
43
|
+
raise ValueError("Invalid boolean value: None. Use True/False.")
|
|
44
|
+
text = str(value).strip().lower()
|
|
45
|
+
if text in _TRUE_VALUES:
|
|
46
|
+
return True
|
|
47
|
+
if text in _FALSE_VALUES:
|
|
48
|
+
return False
|
|
49
|
+
raise ValueError(f"Invalid boolean value: {value!r}. Use True/False.")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def argparse_bool(value: str) -> bool:
|
|
53
|
+
"""argparse-compatible boolean parser using parse_bool()."""
|
|
54
|
+
try:
|
|
55
|
+
return parse_bool(value)
|
|
56
|
+
except ValueError as e:
|
|
57
|
+
raise argparse.ArgumentTypeError(str(e))
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# YAML source resolution
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
def resolve_yaml_sources(
|
|
65
|
+
config_yaml: Optional[Path],
|
|
66
|
+
override_yaml: Optional[Path],
|
|
67
|
+
args_yaml_legacy: Optional[Path],
|
|
68
|
+
) -> Tuple[Optional[Path], Optional[Path], bool]:
|
|
69
|
+
"""Resolve which YAML files to use, raising on conflicting options."""
|
|
70
|
+
if override_yaml is not None and args_yaml_legacy is not None:
|
|
71
|
+
raise click.BadParameter(
|
|
72
|
+
"Use a single YAML source option."
|
|
73
|
+
)
|
|
74
|
+
if args_yaml_legacy is not None:
|
|
75
|
+
return config_yaml, args_yaml_legacy, True
|
|
76
|
+
return config_yaml, override_yaml, False
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def load_merged_yaml_cfg(
|
|
80
|
+
config_yaml: Optional[Path],
|
|
81
|
+
override_yaml: Optional[Path],
|
|
82
|
+
) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any]]:
|
|
83
|
+
"""Load and merge YAML config and override files.
|
|
84
|
+
|
|
85
|
+
Returns ``(merged, config_dict, override_dict)`` so that callers can
|
|
86
|
+
use the individual layers for staged ``apply_yaml_overrides`` and the
|
|
87
|
+
merged dict for ``show_config`` display without re-reading the files.
|
|
88
|
+
"""
|
|
89
|
+
config_dict = load_yaml_dict(config_yaml)
|
|
90
|
+
override_dict = load_yaml_dict(override_yaml)
|
|
91
|
+
merged: Dict[str, Any] = {}
|
|
92
|
+
deep_update(merged, config_dict)
|
|
93
|
+
deep_update(merged, override_dict)
|
|
94
|
+
return merged, config_dict, override_dict
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# File helpers
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
def link_or_copy_file(src: Path, dst: Path) -> bool:
|
|
102
|
+
"""Create a symlink when possible; fall back to copy."""
|
|
103
|
+
try:
|
|
104
|
+
if dst.exists() or dst.is_symlink():
|
|
105
|
+
if dst.is_dir():
|
|
106
|
+
return False
|
|
107
|
+
dst.unlink()
|
|
108
|
+
rel = os.path.relpath(src, start=dst.parent)
|
|
109
|
+
dst.symlink_to(rel)
|
|
110
|
+
return True
|
|
111
|
+
except OSError:
|
|
112
|
+
try:
|
|
113
|
+
shutil.copy2(src, dst)
|
|
114
|
+
return True
|
|
115
|
+
except OSError:
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# ---------------------------------------------------------------------------
|
|
120
|
+
# CLI exception wrapper
|
|
121
|
+
# ---------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
def run_cli(
|
|
124
|
+
fn: Callable[[], None],
|
|
125
|
+
*,
|
|
126
|
+
label: str,
|
|
127
|
+
zero_step_exc: Optional[Type[BaseException]] = None,
|
|
128
|
+
zero_step_msg: Optional[str] = None,
|
|
129
|
+
opt_exc: Optional[Type[BaseException]] = None,
|
|
130
|
+
opt_msg: Optional[str] = None,
|
|
131
|
+
) -> None:
|
|
132
|
+
"""Standard CLI exception handling with consistent messaging."""
|
|
133
|
+
try:
|
|
134
|
+
fn()
|
|
135
|
+
except KeyboardInterrupt:
|
|
136
|
+
click.echo("Interrupted by user.", err=True)
|
|
137
|
+
sys.exit(130)
|
|
138
|
+
except Exception as e:
|
|
139
|
+
if zero_step_exc is not None and isinstance(e, zero_step_exc):
|
|
140
|
+
click.echo(
|
|
141
|
+
zero_step_msg
|
|
142
|
+
or "ERROR: Proposed step length dropped below the minimum allowed (ZeroStepLength).",
|
|
143
|
+
err=True,
|
|
144
|
+
)
|
|
145
|
+
sys.exit(2)
|
|
146
|
+
if opt_exc is not None and isinstance(e, opt_exc):
|
|
147
|
+
msg = opt_msg or "ERROR: Optimization failed - {e}"
|
|
148
|
+
click.echo(msg.format(e=e), err=True)
|
|
149
|
+
sys.exit(3)
|
|
150
|
+
tb = "".join(traceback.format_exception(type(e), e, e.__traceback__))
|
|
151
|
+
click.echo(
|
|
152
|
+
f"Unhandled error during {label}:\n" + textwrap.indent(tb, " "),
|
|
153
|
+
err=True,
|
|
154
|
+
)
|
|
155
|
+
sys.exit(1)
|
|
156
|
+
finally:
|
|
157
|
+
# Release GPU memory (model + Hessian) after CLI command finishes
|
|
158
|
+
# so that subsequent pipeline stages (e.g. tsopt → irc) don't OOM.
|
|
159
|
+
# gc.collect() breaks cyclic refs inside torch.nn.Module.
|
|
160
|
+
gc.collect()
|
|
161
|
+
try:
|
|
162
|
+
import torch
|
|
163
|
+
if torch.cuda.is_available():
|
|
164
|
+
torch.cuda.empty_cache()
|
|
165
|
+
except ImportError:
|
|
166
|
+
pass
|
mlmm/default_group.py
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"""Shared Click group helpers for lazy subcommand loading and bool normalization."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
from collections.abc import Callable, Mapping
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
LazySubcommands = Mapping[str, tuple[str, str, str]]
|
|
11
|
+
BoolOptionsByCommand = Mapping[str, frozenset[str]]
|
|
12
|
+
BoolNegativeAliasesByCommand = Mapping[str, Mapping[str, str]]
|
|
13
|
+
BoolSingleFlagOptionsByCommand = Mapping[str, frozenset[str]]
|
|
14
|
+
PrimaryHelpOptionsByCommand = Mapping[str, frozenset[str]]
|
|
15
|
+
ParserWrapperBoolOptionProviders = Mapping[str, Callable[[], frozenset[str]]]
|
|
16
|
+
|
|
17
|
+
NormalizeBoolArgvFunc = Callable[
|
|
18
|
+
[
|
|
19
|
+
list[str],
|
|
20
|
+
BoolOptionsByCommand,
|
|
21
|
+
BoolOptionsByCommand,
|
|
22
|
+
BoolNegativeAliasesByCommand,
|
|
23
|
+
BoolSingleFlagOptionsByCommand,
|
|
24
|
+
],
|
|
25
|
+
tuple[list[str], bool],
|
|
26
|
+
]
|
|
27
|
+
EnsureHelpAdvancedOptionFunc = Callable[[click.Command], click.Command]
|
|
28
|
+
ConfigureSubcommandHelpVisibilityFunc = Callable[
|
|
29
|
+
[str, click.Command, PrimaryHelpOptionsByCommand], click.Command
|
|
30
|
+
]
|
|
31
|
+
BuildUnavailableCommandFunc = Callable[[str, ImportError], click.Command]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def build_unavailable_command(command_name: str, exc: ImportError) -> click.Command:
|
|
35
|
+
"""Return a placeholder command that reports import failure details at runtime."""
|
|
36
|
+
missing = exc.name if isinstance(exc, ModuleNotFoundError) else None
|
|
37
|
+
msg_lines = [
|
|
38
|
+
f"Command '{command_name}' is unavailable because the module could not be imported."
|
|
39
|
+
]
|
|
40
|
+
if missing:
|
|
41
|
+
msg_lines.append(f"Missing dependency: {missing}")
|
|
42
|
+
msg_lines.append("Install the missing dependency in your runtime environment and retry.")
|
|
43
|
+
else:
|
|
44
|
+
msg_lines.append(f"Import error: {exc}")
|
|
45
|
+
|
|
46
|
+
help_text = (
|
|
47
|
+
f"[Unavailable] {command_name} command.\n"
|
|
48
|
+
"The command failed to import due to a missing dependency."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@click.command(name=command_name, help=help_text)
|
|
52
|
+
def _unavailable() -> None:
|
|
53
|
+
raise click.ClickException("\n".join(msg_lines))
|
|
54
|
+
|
|
55
|
+
return _unavailable
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DefaultGroup(click.Group):
|
|
59
|
+
"""Click group with default subcommand + lazy loading + bool compatibility normalization."""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
*args,
|
|
64
|
+
default: str | None = None,
|
|
65
|
+
lazy_subcommands: LazySubcommands | None = None,
|
|
66
|
+
command_bool_value_options: BoolOptionsByCommand | None = None,
|
|
67
|
+
command_bool_toggle_options: BoolOptionsByCommand | None = None,
|
|
68
|
+
command_bool_toggle_negative_aliases: BoolNegativeAliasesByCommand | None = None,
|
|
69
|
+
command_bool_single_flag_options: BoolSingleFlagOptionsByCommand | None = None,
|
|
70
|
+
parser_wrapper_subcommands: frozenset[str] | None = None,
|
|
71
|
+
parser_wrapper_bool_option_providers: ParserWrapperBoolOptionProviders | None = None,
|
|
72
|
+
subcommand_primary_help_options: PrimaryHelpOptionsByCommand | None = None,
|
|
73
|
+
normalize_bool_argv: NormalizeBoolArgvFunc | None = None,
|
|
74
|
+
ensure_help_advanced_option: EnsureHelpAdvancedOptionFunc | None = None,
|
|
75
|
+
configure_subcommand_help_visibility: ConfigureSubcommandHelpVisibilityFunc | None = None,
|
|
76
|
+
build_unavailable_command: BuildUnavailableCommandFunc = build_unavailable_command,
|
|
77
|
+
**kwargs,
|
|
78
|
+
):
|
|
79
|
+
super().__init__(*args, **kwargs)
|
|
80
|
+
if normalize_bool_argv is None:
|
|
81
|
+
raise ValueError("normalize_bool_argv is required")
|
|
82
|
+
if ensure_help_advanced_option is None:
|
|
83
|
+
raise ValueError("ensure_help_advanced_option is required")
|
|
84
|
+
if configure_subcommand_help_visibility is None:
|
|
85
|
+
raise ValueError("configure_subcommand_help_visibility is required")
|
|
86
|
+
|
|
87
|
+
self._default_cmd = default
|
|
88
|
+
self._lazy_subcommands = dict(lazy_subcommands or {})
|
|
89
|
+
self._lazy_cache: dict[str, click.Command] = {}
|
|
90
|
+
self._command_bool_value_options = dict(command_bool_value_options or {})
|
|
91
|
+
self._command_bool_toggle_options = dict(command_bool_toggle_options or {})
|
|
92
|
+
self._command_bool_toggle_negative_aliases = dict(
|
|
93
|
+
command_bool_toggle_negative_aliases or {}
|
|
94
|
+
)
|
|
95
|
+
self._command_bool_single_flag_options = dict(command_bool_single_flag_options or {})
|
|
96
|
+
self._resolved_bool_options_by_command: dict[
|
|
97
|
+
str, tuple[frozenset[str], frozenset[str], dict[str, str], frozenset[str]]
|
|
98
|
+
] = {}
|
|
99
|
+
self._parser_wrapper_subcommands = set(parser_wrapper_subcommands or set())
|
|
100
|
+
self._parser_wrapper_bool_option_providers = dict(
|
|
101
|
+
parser_wrapper_bool_option_providers or {}
|
|
102
|
+
)
|
|
103
|
+
self._resolved_parser_wrapper_bool_options: dict[str, frozenset[str]] = {}
|
|
104
|
+
self._subcommand_primary_help_options = dict(subcommand_primary_help_options or {})
|
|
105
|
+
self._normalize_bool_argv = normalize_bool_argv
|
|
106
|
+
self._ensure_help_advanced_option = ensure_help_advanced_option
|
|
107
|
+
self._configure_subcommand_help_visibility = configure_subcommand_help_visibility
|
|
108
|
+
self._build_unavailable_command = build_unavailable_command
|
|
109
|
+
|
|
110
|
+
def _resolve_parser_wrapper_bool_options(self, command_name: str) -> frozenset[str]:
|
|
111
|
+
cached = self._resolved_parser_wrapper_bool_options.get(command_name)
|
|
112
|
+
if cached is not None:
|
|
113
|
+
return cached
|
|
114
|
+
|
|
115
|
+
provider = self._parser_wrapper_bool_option_providers.get(command_name)
|
|
116
|
+
if provider is None:
|
|
117
|
+
resolved = frozenset()
|
|
118
|
+
self._resolved_parser_wrapper_bool_options[command_name] = resolved
|
|
119
|
+
return resolved
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
provided = provider()
|
|
123
|
+
except Exception:
|
|
124
|
+
resolved = frozenset()
|
|
125
|
+
self._resolved_parser_wrapper_bool_options[command_name] = resolved
|
|
126
|
+
return resolved
|
|
127
|
+
|
|
128
|
+
normalized: set[str] = set()
|
|
129
|
+
for name in provided:
|
|
130
|
+
if not name.startswith("--"):
|
|
131
|
+
continue
|
|
132
|
+
if name.startswith("--no-"):
|
|
133
|
+
normalized.add(f"--{name[5:]}")
|
|
134
|
+
else:
|
|
135
|
+
normalized.add(name)
|
|
136
|
+
|
|
137
|
+
resolved = frozenset(normalized)
|
|
138
|
+
self._resolved_parser_wrapper_bool_options[command_name] = resolved
|
|
139
|
+
return resolved
|
|
140
|
+
|
|
141
|
+
@staticmethod
|
|
142
|
+
def _long_option_names(option_names: list[str]) -> tuple[str, ...]:
|
|
143
|
+
return tuple(name for name in option_names if name.startswith("--"))
|
|
144
|
+
|
|
145
|
+
def _resolve_bool_options(
|
|
146
|
+
self, ctx: click.Context, command_name: str
|
|
147
|
+
) -> tuple[frozenset[str], frozenset[str], dict[str, str], frozenset[str]]:
|
|
148
|
+
cached = self._resolved_bool_options_by_command.get(command_name)
|
|
149
|
+
if cached is not None:
|
|
150
|
+
return cached
|
|
151
|
+
|
|
152
|
+
bool_value_options = set(
|
|
153
|
+
self._command_bool_value_options.get(command_name, frozenset())
|
|
154
|
+
)
|
|
155
|
+
bool_toggle_options = set(
|
|
156
|
+
self._command_bool_toggle_options.get(command_name, frozenset())
|
|
157
|
+
)
|
|
158
|
+
bool_toggle_negative_aliases = dict(
|
|
159
|
+
self._command_bool_toggle_negative_aliases.get(command_name, {})
|
|
160
|
+
)
|
|
161
|
+
bool_single_flag_options = set(
|
|
162
|
+
self._command_bool_single_flag_options.get(command_name, frozenset())
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
bool_toggle_options.update(
|
|
166
|
+
self._resolve_parser_wrapper_bool_options(command_name)
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
command = self.get_command(ctx, command_name)
|
|
170
|
+
if command is not None:
|
|
171
|
+
for param in command.params:
|
|
172
|
+
if not isinstance(param, click.Option):
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
positive_long_names = self._long_option_names(param.opts)
|
|
176
|
+
if not positive_long_names:
|
|
177
|
+
continue
|
|
178
|
+
|
|
179
|
+
negative_long_names = self._long_option_names(param.secondary_opts)
|
|
180
|
+
if param.is_bool_flag:
|
|
181
|
+
if negative_long_names:
|
|
182
|
+
bool_toggle_options.update(positive_long_names)
|
|
183
|
+
first_negative = negative_long_names[0]
|
|
184
|
+
for index, positive_name in enumerate(positive_long_names):
|
|
185
|
+
negative_name = (
|
|
186
|
+
negative_long_names[index]
|
|
187
|
+
if index < len(negative_long_names)
|
|
188
|
+
else first_negative
|
|
189
|
+
)
|
|
190
|
+
bool_toggle_negative_aliases.setdefault(
|
|
191
|
+
positive_name, negative_name
|
|
192
|
+
)
|
|
193
|
+
else:
|
|
194
|
+
bool_single_flag_options.update(positive_long_names)
|
|
195
|
+
continue
|
|
196
|
+
|
|
197
|
+
if isinstance(param.type, click.types.BoolParamType):
|
|
198
|
+
bool_value_options.update(positive_long_names)
|
|
199
|
+
|
|
200
|
+
resolved = (
|
|
201
|
+
frozenset(bool_value_options),
|
|
202
|
+
frozenset(bool_toggle_options),
|
|
203
|
+
bool_toggle_negative_aliases,
|
|
204
|
+
frozenset(bool_single_flag_options),
|
|
205
|
+
)
|
|
206
|
+
self._resolved_bool_options_by_command[command_name] = resolved
|
|
207
|
+
return resolved
|
|
208
|
+
|
|
209
|
+
def parse_args(self, ctx, args):
|
|
210
|
+
show_help_or_version = any(a in ("-h", "--help", "--version") for a in args)
|
|
211
|
+
|
|
212
|
+
if self._default_cmd is not None and not show_help_or_version:
|
|
213
|
+
if not args or args[0].startswith("-"):
|
|
214
|
+
args.insert(0, self._default_cmd)
|
|
215
|
+
|
|
216
|
+
bool_value_options = self._command_bool_value_options
|
|
217
|
+
bool_toggle_options = self._command_bool_toggle_options
|
|
218
|
+
bool_toggle_negative_aliases = self._command_bool_toggle_negative_aliases
|
|
219
|
+
bool_single_flag_options = self._command_bool_single_flag_options
|
|
220
|
+
if args and not args[0].startswith("-"):
|
|
221
|
+
command_name = args[0]
|
|
222
|
+
(
|
|
223
|
+
command_bool_value_options,
|
|
224
|
+
command_bool_toggle_options,
|
|
225
|
+
command_toggle_negative_aliases,
|
|
226
|
+
command_bool_single_flag_options,
|
|
227
|
+
) = self._resolve_bool_options(ctx, command_name)
|
|
228
|
+
bool_value_options = {command_name: command_bool_value_options}
|
|
229
|
+
bool_toggle_options = {command_name: command_bool_toggle_options}
|
|
230
|
+
bool_toggle_negative_aliases = {
|
|
231
|
+
command_name: command_toggle_negative_aliases
|
|
232
|
+
}
|
|
233
|
+
bool_single_flag_options = {command_name: command_bool_single_flag_options}
|
|
234
|
+
|
|
235
|
+
args, _ = self._normalize_bool_argv(
|
|
236
|
+
args,
|
|
237
|
+
bool_value_options,
|
|
238
|
+
bool_toggle_options,
|
|
239
|
+
bool_toggle_negative_aliases,
|
|
240
|
+
bool_single_flag_options,
|
|
241
|
+
)
|
|
242
|
+
return super().parse_args(ctx, args)
|
|
243
|
+
|
|
244
|
+
@staticmethod
|
|
245
|
+
def _silence_pysisyphus_loggers():
|
|
246
|
+
"""Remove pysisyphus file handlers and suppress log output.
|
|
247
|
+
|
|
248
|
+
pysisyphus creates FileHandler + StreamHandler in its various
|
|
249
|
+
``__init__.py`` files at import time, overriding any prior
|
|
250
|
+
level settings. This method must run *after* the lazy import
|
|
251
|
+
has loaded the subcommand module (and thus pysisyphus).
|
|
252
|
+
"""
|
|
253
|
+
import logging as _logging
|
|
254
|
+
|
|
255
|
+
_PYSIS_LOGGERS = (
|
|
256
|
+
"pysisyphus", "calculator", "cos", "dimer", "dynamics",
|
|
257
|
+
"gdiis", "internal_coords", "irc", "optimizer",
|
|
258
|
+
"tsoptimizer", "mwfn", "stocastic", "wfoverlap",
|
|
259
|
+
)
|
|
260
|
+
for name in _PYSIS_LOGGERS:
|
|
261
|
+
lg = _logging.getLogger(name)
|
|
262
|
+
lg.setLevel(_logging.CRITICAL + 1)
|
|
263
|
+
lg.propagate = False
|
|
264
|
+
for h in lg.handlers[:]:
|
|
265
|
+
lg.removeHandler(h)
|
|
266
|
+
try:
|
|
267
|
+
h.close()
|
|
268
|
+
except Exception:
|
|
269
|
+
pass
|
|
270
|
+
|
|
271
|
+
def invoke(self, ctx):
|
|
272
|
+
# Add a leading blank line for subcommands (except "all") to separate CLI tool logs.
|
|
273
|
+
if ctx.invoked_subcommand and ctx.invoked_subcommand != "all":
|
|
274
|
+
click.echo()
|
|
275
|
+
# Suppress pysisyphus loggers AFTER lazy import has loaded the
|
|
276
|
+
# subcommand module (which triggers pysisyphus __init__ file handlers).
|
|
277
|
+
self._silence_pysisyphus_loggers()
|
|
278
|
+
return super().invoke(ctx)
|
|
279
|
+
|
|
280
|
+
def list_commands(self, ctx):
|
|
281
|
+
cmds = set(super().list_commands(ctx))
|
|
282
|
+
cmds.update(self._lazy_subcommands.keys())
|
|
283
|
+
return sorted(cmds)
|
|
284
|
+
|
|
285
|
+
def get_command(self, ctx, cmd_name):
|
|
286
|
+
cmd = super().get_command(ctx, cmd_name)
|
|
287
|
+
if cmd is not None:
|
|
288
|
+
if cmd_name not in self._parser_wrapper_subcommands:
|
|
289
|
+
cmd = self._ensure_help_advanced_option(cmd)
|
|
290
|
+
return self._configure_subcommand_help_visibility(
|
|
291
|
+
cmd_name, cmd, self._subcommand_primary_help_options
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
lazy_spec = self._lazy_subcommands.get(cmd_name)
|
|
295
|
+
if lazy_spec is None:
|
|
296
|
+
return None
|
|
297
|
+
|
|
298
|
+
cached = self._lazy_cache.get(cmd_name)
|
|
299
|
+
if cached is not None:
|
|
300
|
+
return cached
|
|
301
|
+
|
|
302
|
+
module_name, attr_name, _ = lazy_spec
|
|
303
|
+
try:
|
|
304
|
+
module = importlib.import_module(module_name, package=__package__)
|
|
305
|
+
loaded_cmd = getattr(module, attr_name)
|
|
306
|
+
except (ModuleNotFoundError, ImportError) as exc:
|
|
307
|
+
loaded_cmd = self._build_unavailable_command(cmd_name, exc)
|
|
308
|
+
|
|
309
|
+
if cmd_name not in self._parser_wrapper_subcommands:
|
|
310
|
+
loaded_cmd = self._ensure_help_advanced_option(loaded_cmd)
|
|
311
|
+
loaded_cmd = self._configure_subcommand_help_visibility(
|
|
312
|
+
cmd_name, loaded_cmd, self._subcommand_primary_help_options
|
|
313
|
+
)
|
|
314
|
+
self._lazy_cache[cmd_name] = loaded_cmd
|
|
315
|
+
return loaded_cmd
|
|
316
|
+
|
|
317
|
+
def format_commands(self, ctx, formatter):
|
|
318
|
+
rows = []
|
|
319
|
+
for subcommand in self.list_commands(ctx):
|
|
320
|
+
lazy_spec = self._lazy_subcommands.get(subcommand)
|
|
321
|
+
if lazy_spec is not None:
|
|
322
|
+
rows.append((subcommand, lazy_spec[2]))
|
|
323
|
+
continue
|
|
324
|
+
|
|
325
|
+
cmd = super().get_command(ctx, subcommand)
|
|
326
|
+
if cmd is None or cmd.hidden:
|
|
327
|
+
continue
|
|
328
|
+
rows.append(
|
|
329
|
+
(
|
|
330
|
+
subcommand,
|
|
331
|
+
cmd.get_short_help_str(formatter.width - 6 - len(subcommand)),
|
|
332
|
+
)
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
if rows:
|
|
336
|
+
with formatter.section("Commands"):
|
|
337
|
+
formatter.write_dl(rows)
|