"""Utilities for CLI functions."""fromcollections.abcimportIterablefromcontextlibimportsuppressimportloggingimportrefromtypingimportAnyfromtypingimportClassVarimportclickfromautojobimportSETTINGSfromautojob.coordinator.validationimportval_to_nativefromautojob.hpcimportconvertfromautojob.utils.schemasimportUnsetlogger=logging.getLogger(__name__)
[docs]classMemoryFloat(click.ParamType):"""A float representing an amount of memory."""name:ClassVar[str]="memory float"
[docs]defconvert(self,value:str|float,param,ctx)->float:"""Convert a memory specification into bytes."""ifnotisinstance(value,str|float):msg=f"{param.name} should be a string or a float: {value!r}"self.fail(msg,param=param,ctx=ctx)withsuppress(ValueError):returnfloat(value)ifmatch:=re.match(r"(?P<memory>\d+(?:\.\d+)?)(?P<units>(?:K|M|G)(?:B)?)",value):memory=float(match.group("memory"))units=match.group("units")returnconvert(memory=memory,from_units=units,to_units="B")msg=f"'{value}' is not a valid memory specification"self.fail(msg,param=param,ctx=ctx)
[docs]defmods_to_dict(_:Any,param:str,value:Iterable[str])->dict[str,Any]:"""Convert an iterable of key-value pairs to a dictionary. Args: _: The first argument is ignored but retained for `click` compatibility. param: The name of the parameter being set (e.g., `calc_mods` or `slurm_mods`). value: An iterable of key-value pairs should exist as a string in the form "key=value". Note that only those values supported by `~validation.val_to_native` can be correctly parsed. Returns: A dictionary mapping calculator parameter names to their Python values. """ifnotisinstance(value,Iterable):msg=f"Something is wrong. {param} should be an iterable: {value!r}"raiseclick.BadParameter(message=msg)mods={}forxinvalue:parameter,*assignment_value=x.split("=",maxsplit=1)ifnotparameteror"="notinx:msg=("Incorrect parameter format. Each argument specified to "f"{param} must have the form 'key=value' or 'key='")raiseclick.BadParameter(message=msg)ifassignment_value:mods[parameter]=val_to_native(assignment_value[0])else:mods[parameter]=Unsetreturnmods
# * Define a decorator (e.g., @reconstructed) which sets this as the object in# * the context (e.g., ctx.obj = construct_cli_call(ctx=ctx, allowed=allowed))
[docs]defconstruct_cli_call(allowed:list[str]|None=None,)->str:"""Construct the original CLI call. Args: allowed: A list of strings indicating which parameters are to be considered to reconstruct the CLI call. Returns: A string representing the command-line call that would produce the present behaviour. """logger.debug("Constructing CLI call")allowed=[]ifallowedisNoneelseallowed# This works so long as the CLI only accepts optionsctx=click.get_current_context()args:list[str]=[]forparam,valueinctx.params.items():if(ctx.get_parameter_source(param)==click.core.ParameterSource.COMMANDLINEandparaminallowed):ifisinstance(value,list|tuple):forelementinvalue:args.append(f'--{param.rstrip("s").replace("_","-")}'f'="{str(element).split("/")[-1]}"')elifisinstance(value,dict):forkey,elementinvalue.items():args.append(f'--{param.rstrip("s").replace("_","-")}'f'="{key}={element!s}"')# This won't work if flag_value=Falseelifisinstance(value,bool)andvalue:args.append(f'--{param.replace("_","-")}')elifparam=="verbosity"andvalue>0:args.append(f"-{'v'*value}")else:args.append(f'--{param.replace("_","-")}="{value}"')command=f"{ctx.command.name}{' '.join(args)}".strip()logger.debug(f"Successfully constructed CLI call: {command}")returncommand
[docs]defconfigure_settings(config:dict[str,Any])->None:"""Set redefine autojob settings. Args: config: A dictionary mapping autojob settings names to their desired values. """forsetting,valueinconfig.items():attr=setting.upper()ifhasattr(SETTINGS,attr):setattr(SETTINGS,attr,value)else:logger.warning(f"Unknown setting provided for configuration: {value}")