Source code for woom.hosts

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Host specific configuration
"""

import fnmatch
import functools
import os
import socket

from . import conf as wconf
from . import env as wenv
from . import job as wjob
from .__init__ import WoomError

thisdir = os.path.dirname(__file__)

CFGSPECS_FILE = os.path.join(thisdir, "hosts.ini")

CFG_DEFAULT_FILE = os.path.join(thisdir, "hosts.cfg")


[docs] class HostError(WoomError): pass
[docs] class HostManager:
[docs] def __init__(self): """Initialize host manager with default configuration""" self._config = wconf.load_cfg(CFG_DEFAULT_FILE, CFGSPECS_FILE) self._host = None self._config_files = []
@property def config(self): """Configuration as loaded from file :file:`hosts.cfg` (:class:`~configobj.ConfigObj`)""" return self._config
[docs] def load_config(self, cfgfile): """Load a user configuration file .. note:: It is merged with the current one Parameters ---------- cfgfile: str A valid config file Return ------ configobj.ConfigObj """ self._config.merge(wconf.load_cfg(cfgfile, CFGSPECS_FILE)) self._config_files.append(cfgfile) return self._config
[docs] def to_json_entry(self): return self._config_files
[docs] @classmethod def from_config_files(cls, *config_files): hostmanager = cls() for config_file in config_files: hostmanager.load(config_file) return hostmanager
[docs] def get_host(self, name): """Get a :class:`Host` instance from its name""" return Host(name, self.config[name])
[docs] def infer_host(self): """Infer host and get a :class:`Host` instance""" hostname = socket.getfqdn() for name, config in self.config.items(): if name == "local": continue for pattern in config["patterns"]: if fnmatch.fnmatch(hostname, pattern): return self.get_host(name) return self.get_host("local")
[docs] class Host:
[docs] def __init__(self, name, config): """Initialize a host Parameters ---------- name : str Host name config : configobj.Section Host configuration section """ self._name = name self._config = config self._env = None self._scheduler = None
@property def name(self): """Host name as defined in the configuration (:class:`str`)""" return self._name def __str__(self): return self.name @property def config(self): """Dict configuration of this host as loaded from file :file:`hosts.cfg` (:class:`dict`)""" return self._config.dict() # def __getitem__(self, key): # return self.config[key] @property def scheduler(self): """Scheduler like background, pbsproc or slurm""" return self.config["scheduler"]
[docs] @functools.lru_cache def get_jobmanager(self): # , session): """Get a :mod:`~woom.job` manager instance Returns ------- woom.job.BackgroundJobManager or woom.job.PbsproJobManager or woom.job.SlurmJobManager """ return wjob.BackgroundJobManager.from_scheduler(self.config["scheduler"]) # , session)
@property def module_setup(self): """Command to declare the :command:`module` command (:class:`str`)""" return self.config["module_setup"] @property def queues(self): """Correspondance between generic and real queue names (:class:`dict`)""" return self.config["queues"]
[docs] def get_queue(self, name): """Get a queue real name from its generic name Accesses ``self._config`` directly instead of going through ``self.config`` (which calls ``Section.dict()``). After a ``ConfigObj.merge()``, ``dict()`` may return spec defaults instead of the user's values. See also -------- queues """ queues = self._config.get("queues", {}) val = queues.get(name) if hasattr(queues, "get") else None return val if val else name
[docs] def get_params(self): """Get a context dict for formatting task commandlines with jinja In merges the following contents: - The ``dirs`` config section with key suffixed with "dir" and with the user "~" symbol and environment variables expanded. Return ------ dict """ params = {} for dname, dval in self.config["dirs"].items(): if dval: dval = os.path.expanduser(os.path.expandvars(dval)) params[dname + "_dir"] = dval return params
[docs] @functools.cache def get_env(self, name): """Get a :class:`EnvConfig` instance from a env config name""" # Default env if name is None: return wenv.EnvConfig() # Registered? if name not in self.config["envs"]: available = ', '.join(self.config["envs"]) raise HostError(f"Invalid environment: {name}. Please choose one of: {available}") cfg = self.config["envs"][name] # Declare directories as woom env variables env_vars = {} for dname, dval in self.config["dirs"].items(): if dval is not None: dval = os.path.expanduser(os.path.expandvars(dval)) env_vars["WOOM_" + dname.upper() + "_DIR"] = dval env_vars.update(cfg["vars"]["set"]) # Get registered env return wenv.EnvConfig( raw_text=cfg["raw_text"], vars_forward=cfg["vars"]["forward"], vars_set=env_vars, vars_append=cfg["vars"]["append"], vars_prepend=cfg["vars"]["prepend"], module_setup=self.config["module_setup"], module_use=cfg["modules"]["use"], module_load=cfg["modules"]["load"], conda_setup=self.config["conda_setup"], conda_activate=cfg["conda_activate"], uv_venv=cfg["uv_venv"], name=name, )