Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 291 & 295 #296

Merged
merged 2 commits into from
May 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions scabha/basetypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ def get_filelikes(dtype, value, filelikes=None):

filelikes = set() if filelikes is None else filelikes

if value is UNSET or type(value) is UNSET:
return []

origin = get_origin(dtype)
args = get_args(dtype)

Expand Down
1 change: 1 addition & 0 deletions scabha/configuratt/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ class FailRecord(object):
origin: Optional[str] = None
modulename: Optional[str] = None
fname: Optional[str] = None
warn: bool = True

@dataclass
class RequirementRecord(object):
Expand Down
52 changes: 35 additions & 17 deletions scabha/configuratt/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,11 @@ def load_include_files(keyword):
if match:
incl = match.group(1)
flags = set([x.strip().lower() for x in match.group(2).split(",")])
warn = 'warn' in flags
optional = 'optional' in flags
else:
flags = {}
warn = optional = False

# check for (location)filename.yaml or (location)/filename.yaml style
match = re.match("^\\((.+)\\)/?(.+)$", incl)
Expand All @@ -232,37 +235,52 @@ def load_include_files(keyword):
if modulename.startswith("."):
filename = os.path.join(os.path.dirname(pathname), modulename, filename)
if not os.path.exists(filename):
if 'optional' in flags:
dependencies.add_fail(FailRecord(filename, pathname))
if 'warn' in flags:
if optional:
dependencies.add_fail(FailRecord(filename, pathname,warn=warn))
if warn:
print(f"Warning: unable to find optional include {incl} ({filename})")
continue
raise ConfigurattError(f"{errloc}: {keyword} {incl} does not exist")
else:
try:
mod = importlib.import_module(modulename)
except ImportError as exc:
if 'optional' in flags:
dependencies.add_fail(FailRecord(incl, pathname, modulename=modulename, fname=filename))
if 'warn' in flags:
if optional:
dependencies.add_fail(FailRecord(incl, pathname, modulename=modulename,
fname=filename, warn=warn))
if warn:
print(f"Warning: unable to import module for optional include {incl}")
continue
raise ConfigurattError(f"{errloc}: {keyword} {incl}: can't import {modulename} ({exc})")

filename = os.path.join(os.path.dirname(mod.__file__), filename)
if mod.__file__ is not None:
path = os.path.dirname(mod.__file__)
else:
path = getattr(mod, '__path__', None)
if path is None:
if optional:
dependencies.add_fail(FailRecord(incl, pathname, modulename=modulename,
fname=filename, warn=warn))
if warn:
print(f"Warning: unable to resolve path for optional include {incl}, does {modulename} contain __init__.py?")
continue
raise ConfigurattError(f"{errloc}: {keyword} {incl}: can't resolve path for {modulename}, does it contain __init__.py?")
path = path[0]

filename = os.path.join(path, filename)
if not os.path.exists(filename):
if 'optional' in flags:
dependencies.add_fail(FailRecord(incl, pathname, modulename=modulename, fname=filename))
if 'warn' in flags:
if optional:
dependencies.add_fail(FailRecord(incl, pathname, modulename=modulename,
fname=filename, warn=warn))
if warn:
print(f"Warning: unable to find optional include {incl}")
continue
raise ConfigurattError(f"{errloc}: {keyword} {incl}: {filename} does not exist")
# absolute path -- one candidate
elif os.path.isabs(incl):
if not os.path.exists(incl):
if 'optional' in flags:
dependencies.add_fail(FailRecord(incl, pathname))
if 'warn' in flags:
if optional:
dependencies.add_fail(FailRecord(incl, pathname, warn=warn))
if warn:
print(f"Warning: unable to find optional include {incl}")
continue
raise ConfigurattError(f"{errloc}: {keyword} {incl} does not exist")
Expand All @@ -275,9 +293,9 @@ def load_include_files(keyword):
if os.path.exists(filename):
break
else:
if 'optional' in flags:
dependencies.add_fail(FailRecord(incl, pathname))
if 'warn' in flags:
if optional:
dependencies.add_fail(FailRecord(incl, pathname, warn=warn))
if warn:
print(f"Warning: unable to find optional include {incl}")
continue
raise ConfigurattError(f"{errloc}: {keyword} {incl} not found in {':'.join(paths)}")
Expand Down
18 changes: 14 additions & 4 deletions stimela/backends/flavours/python_flavours.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@ def get_python_interpreter_args(cab: Cab, subst: Dict[str, Any], virtual_env: Op
# get virtual env, if specified
if virtual_env:
virtual_env = os.path.expanduser(virtual_env)
interpreter = f"{virtual_env}/bin/python"
interpreter = f"{virtual_env}/bin/{cab.flavour.interpreter_binary}"
if not os.path.isfile(interpreter):
raise CabValidationError(f"virtual environment {virtual_env} doesn't exist")
raise CabValidationError(f"{interpreter} doesn't exist")
else:
interpreter = "python"
interpreter = cab.flavour.interpreter_binary

return [interpreter, "-u"]
args = cab.flavour.interpreter_command.format(python=interpreter).split()

return args


@dataclass
Expand All @@ -67,6 +69,10 @@ class PythonCallableFlavour(_CallableFlavour):
expected to be in the form of [package.]module.function
"""
kind: str = "python"
# name of python binary to use
interpreter_binary: str = "python"
# Full command used to launch interpreter. {python} gets substituted for the interpreter path
interpreter_command: str = "{python} -u"
# don't log full command by default, as that's full of code
log_full_command: bool = False

Expand Down Expand Up @@ -155,6 +161,10 @@ class PythonCodeFlavour(_BaseFlavour):
output_vars: bool = True
# if True, command will have {}-substitutions done on it
subst: bool = False
# name of python binary to use
interpreter_binary: str = "python"
# Full command used to launch interpreter. {python} gets substituted for the interpreter path
interpreter_command: str = "{python} -u"
# don't log full command by default, as that's full of code
log_full_command: bool = False

Expand Down
7 changes: 4 additions & 3 deletions stimela/commands/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,10 @@ def load_recipe_files(filenames: List[str]):
full_deps.update(deps)

# warn user if any includes failed
if full_deps.fails:
logger().warning(f"{len(full_deps.fails)} optional includes were not found, some cabs may not be available")
for path, dep in full_deps.fails.items():
failed_includes = {path: dep for path, dep in full_deps.fails.items() if dep.warn}
if failed_includes:
logger().warning(f"{len(failed_includes)} optional includes were not found")
for path, dep in failed_includes.items():
logger().warning(f" {path} (from {dep.origin})")

# merge into full config dependencies
Expand Down
4 changes: 4 additions & 0 deletions stimela/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from omegaconf.omegaconf import MISSING, OmegaConf
from omegaconf.errors import OmegaConfBaseException
from collections import OrderedDict
import psutil

from yaml.error import YAMLError
import stimela
Expand Down Expand Up @@ -268,9 +269,12 @@ def _load(conf, config_file):
runtime = dict(
date=_ds,
time=_ts, datetime=f"{_ds}-{_ts}",
ncpu=psutil.cpu_count(logical=True),
node=platform.node().split('.', 1)[0],
hostname=platform.node(),
env={key: value.replace('${', '\${') for key, value in os.environ.items()})
runtime['ncpu-logical'] = psutil.cpu_count(logical=True)
runtime['ncpu-physical'] = psutil.cpu_count(logical=False)

conf.run = OmegaConf.create(runtime)

Expand Down
Loading