hpcman.software.recipe
Describes the Recipe class and validates recipes using pydantic
Classes
BaseRecipe
Bases: BaseModel
Source code in hpcman/hpcman/software/recipe.py
| class BaseRecipe(BaseModel): # type: ignore
model_config = ConfigDict(validate_assignment=True, arbitrary_types_allowed=True)
recipe_name: NoSpaces
name: NoSpaces
method: Annotated[InstallMethod, Field(frozen=True)]
tags: List[str]
exes: List[str]
libs: Optional[List[str]] = None
category: Category
docs: Optional[List[AnyUrl]] = None
refs: Optional[List[AnyUrl]] = None
install_hook: Optional[str] = None
help_check: Optional[HelpCheck] = None
version_check: Optional[VersionCheck] = None
export: Optional[str] = None
msgtype: Optional[MsgType] = MsgType.STDOUT # No longer used.
patches: Optional[List[str]] = None
patch_dir: Optional[str] = None
patch_opts: Optional[str] = None
patch_order: Optional[PatchOrder] = None
|
Fields
| Name |
Type |
Description |
recipe_name |
NoSpaces |
|
name |
NoSpaces |
|
method |
InstallMethod |
|
tags |
List[str] |
|
exes |
List[str] |
|
libs |
Optional[List[str]] |
|
category |
Category |
|
docs |
Optional[List[AnyUrl]] |
|
refs |
Optional[List[AnyUrl]] |
|
install_hook |
Optional[str] |
|
help_check |
Optional[HelpCheck] |
|
version_check |
Optional[VersionCheck] |
|
export |
Optional[str] |
|
msgtype |
Optional[MsgType] |
|
patches |
Optional[List[str]] |
|
patch_dir |
Optional[str] |
|
patch_opts |
Optional[str] |
|
patch_order |
Optional[PatchOrder] |
|
CondaRecipe
Bases: BaseRecipe
Source code in hpcman/hpcman/software/recipe.py
| class CondaRecipe(BaseRecipe): # type: ignore
pkgs: List[str]
pypi: Optional[List[str]] = None
channels: Optional[List[str]] = None
fix_r: bool
fix_python: bool
fix_perl: bool
priority: Optional[Priority] = Priority.STRICT
cuda: Optional[Cuda] = None
@field_validator("fix_r", "fix_python", "fix_perl", mode="before")
@classmethod
def set_false_if_none(cls, v: Optional[bool], info: ValidationInfo) -> bool:
if v is None:
v = False
return v
|
Fields
| Name |
Type |
Description |
pkgs |
List[str] |
|
pypi |
Optional[List[str]] |
|
channels |
Optional[List[str]] |
|
fix_r |
bool |
|
fix_python |
bool |
|
fix_perl |
bool |
|
priority |
Optional[Priority] |
|
cuda |
Optional[Cuda] |
|
Methods
set_false_if_none
def set_false_if_none(
v: Optional[bool],
info: ValidationInfo
) -> bool
Source code in hpcman/hpcman/software/recipe.py
| @field_validator("fix_r", "fix_python", "fix_perl", mode="before")
@classmethod
def set_false_if_none(cls, v: Optional[bool], info: ValidationInfo) -> bool:
if v is None:
v = False
return v
|
PixiRecipe
Bases: BaseRecipe
Source code in hpcman/hpcman/software/recipe.py
| class PixiRecipe(BaseRecipe): # type: ignore
pkg: str
channels: Optional[List[str]] = None
|
Fields
| Name |
Type |
Description |
pkg |
str |
|
channels |
Optional[List[str]] |
|
PipxRecipe
Bases: BaseRecipe
Source code in hpcman/hpcman/software/recipe.py
| class PipxRecipe(BaseRecipe): # type: ignore
pkg: str
python_ver: Optional[PythonVer] = None
pipx_backend: Optional[PipxBackend] = PipxBackend.UV
|
Fields
| Name |
Type |
Description |
pkg |
str |
|
python_ver |
Optional[PythonVer] |
|
pipx_backend |
Optional[PipxBackend] |
|
Functions
load_recipe
def load_recipe(
recipe_file: Path
) -> BaseRecipe
Source code in hpcman/hpcman/software/recipe.py
| @log_call()
def load_recipe(recipe_file: Path) -> BaseRecipe:
with open(recipe_file, "r") as recipefh:
logger.trace('yaml = YAML(typ="rt", pure=True)')
yaml = YAML(typ="rt", pure=True) # Keeps key order rather than sorting
logger.trace(f"raw_recipe = yaml.load({recipe_file.as_posix()})")
raw_recipe = yaml.load(recipefh)
logger.trace(raw_recipe)
try:
raw_recipe["method"]
except KeyError:
rprint("[red]Unable to detect install method[/red]")
rprint(f"Check recipe from {recipe_file.as_posix} and try again")
exit(1)
return get_finished_recipe(**raw_recipe)
|
get_finished_recipe
def get_finished_recipe(
kwargs = {}
) -> BaseRecipe
Generate proper Recipe Class
Source code in hpcman/hpcman/software/recipe.py
| @log_call()
def get_finished_recipe(**kwargs) -> BaseRecipe:
"""Generate proper Recipe Class"""
method = kwargs.get("method")
if method == InstallMethod.CONDA:
return CondaRecipe(**kwargs)
elif method == InstallMethod.COMPILE:
return BaseRecipe(**kwargs)
elif method == InstallMethod.PIPX:
if "pkgs" in kwargs:
if len(pkgs := kwargs.pop("pkgs", [])) == 1:
kwargs["pkg"] = pkgs[0]
else:
rprint("[red]Pipx recipe only supports one package[/red]")
rprint("Either update your recipe with one pkg and/or specify additional pkgs with --libs.")
exit(1)
return PipxRecipe(**kwargs)
elif method == InstallMethod.PIXI:
if "pkgs" in kwargs:
if len(pkgs := kwargs.pop("pkgs", [])) == 1:
kwargs["pkg"] = pkgs[0]
else:
rprint("[red]Pixi recipe only supports one package[/red]")
rprint("Either update your recipe with one pkg or use the conda install method.")
exit(1)
return PixiRecipe(**kwargs)
else:
rprint(f"[red]Unknown method {method}[/red]")
return BaseRecipe(**kwargs)
|
export_and_dump
def export_and_dump(
recipe_dir: Path,
recipe: BaseRecipe
) -> Path
Makes directory and dumps recipe there
Source code in hpcman/hpcman/software/recipe.py
| @log_call()
def export_and_dump(recipe_dir: Path, recipe: BaseRecipe) -> Path:
"""Makes directory and dumps recipe there"""
new_recipe = Path(recipe_dir / recipe.recipe_name)
new_recipe.mkdir(mode=0o775, exist_ok=True)
dump_model(Path(new_recipe / RECIPE_FN), recipe)
return new_recipe
|
dump_model
def dump_model(
recipe_file: Path,
recipe: BaseModel,
readonly: bool = False,
force: bool = False
) -> None
Dumps recipe to yaml file.
Source code in hpcman/hpcman/software/recipe.py
| @log_call()
def dump_model(recipe_file: Path, recipe: BaseModel, readonly: bool = False, force: bool = False) -> None:
"""Dumps recipe to yaml file."""
writer = YAML(typ="rt", pure=True)
if force and recipe_file.exists() and not os.access(recipe_file, os.W_OK):
recipe_file.chmod(stat.S_IWUSR)
try:
to_yaml_file(recipe_file, recipe, custom_yaml_writer=writer)
except PermissionError:
rprint(f"You do not have permission to over-write {recipe_file.as_posix()}.")
rprint("Use the '--force' option and try again. Also, check your '$USER'")
return None
if readonly:
recipe_file.chmod(mode=0o444)
|
get_recipe_list
def get_recipe_list(
recipe_dir: Path
) -> List[str]
Get list of recipe names.
Source code in hpcman/hpcman/software/recipe.py
| def get_recipe_list(recipe_dir: Path) -> List[str]:
"""Get list of recipe names."""
recipes = [x.name for x in recipe_dir.iterdir() if x.is_dir() and Path(x / RECIPE_FN).exists()]
return recipes
|
handle_recipe_errors
def handle_recipe_errors(
e: ValidationError
) -> None
Handles recipe errors and prints messages
Source code in hpcman/hpcman/software/recipe.py
| def handle_recipe_errors(e: ValidationError) -> None:
"""Handles recipe errors and prints messages"""
rprint(f"Detected {e.error_count()} validation error(s):")
for error in e.errors():
if (
any(option in error["loc"] for option in ("recipe_name", "name"))
and error["type"] == "string_pattern_mismatch"
):
error["msg"] = "'recipe_name' and 'name' must not contain spaces. Check settings and try again."
rprint(f" => Fields: {error['loc']}")
rprint(f" => {error['msg']} -> type={error['type']}, input_value={error['input']}\n")
rprint("Please provide these values in the hpcrecipe.yaml file or as command-line options and try again.")
exit(1)
|
update_recipe
def update_recipe(
recipe: BaseRecipe,
kwargs = {}
) -> BaseRecipe
Update recipe with new info
Source code in hpcman/hpcman/software/recipe.py
| @log_call()
def update_recipe(recipe: BaseRecipe, **kwargs) -> BaseRecipe:
"""Update recipe with new info"""
for k, v in kwargs.items():
if hasattr(recipe, k):
setattr(recipe, k, v)
else:
logger.warning(f"Attempting to set option '{k}' which is not present for method '{recipe.method}'.")
return recipe
|
export_model_schema
def export_model_schema(
method: InstallMethod
) -> None
Exports schema from pydantic model
Source code in hpcman/hpcman/software/recipe.py
| def export_model_schema(method: InstallMethod) -> None:
"""Exports schema from pydantic model"""
if method is InstallMethod.CONDA:
recipe = CondaRecipe
elif method is InstallMethod.PIXI:
recipe = PixiRecipe
elif method is InstallMethod.PIPX:
recipe = PipxRecipe
else:
rprint(f"Method '{method.value}' hasn't been implemented yet.")
exit()
print_json(json.dumps(recipe.model_json_schema()))
|
load_conda_yaml
def load_conda_yaml(
yaml_file: FileText
) -> tuple[Channels | None, Packages | None]
Source code in hpcman/hpcman/software/recipe.py
| @log_call()
def load_conda_yaml(yaml_file: FileText) -> tuple[Channels | None, Packages | None]:
yaml = YAML(typ="rt", pure=True) # Keeps key order rather than sorting
conda_recipe = yaml.load(yaml_file)
try:
channels: Channels | None = conda_recipe["channels"]
except KeyError:
channels = None
try:
packages: Packages | None = conda_recipe["dependencies"]
except KeyError:
packages = None
return (channels, packages)
|