Source code for datalad_container.utils

"""Collection of common utilities"""

from __future__ import annotations

# the pathlib equivalent is only available in PY3.12
from os.path import lexists
from pathlib import (

from datalad.distribution.dataset import Dataset
from import external_versions

[docs] def get_container_command(): for command in ["apptainer", "singularity"]: container_system_version = external_versions[f"cmd:{command}"] if container_system_version: return command else: raise RuntimeError("Did not find apptainer or singularity")
[docs] def get_container_configuration( ds: Dataset, name: str | None = None, ) -> dict: """Report all container-related configuration in a dataset Such configuration is identified by the item name pattern:: datalad.containers.<container-name>.<item-name> Parameters ---------- ds: Dataset Dataset instance to report configuration on. name: str, optional If given, the reported configuration will be limited to the container with this exact name. In this case, only a single ``dict`` is returned, not nested dictionaries. Returns ------- dict Keys are the names of configured containers and values are dictionaries with their respective configuration items (with the ``datalad.containers.<container-name>.`` prefix removed from their keys). If `name` is given, only a single ``dict`` with the configuration items of the matching container is returned (i.e., there will be no outer ``dict`` with container names as keys). If not (matching) container configuration exists, and empty dictionary is returned. """ var_prefix = 'datalad.containers.' containers = {} # all info is in the dataset config! for var, value in ds.config.items(): if not var.startswith(var_prefix): # not an interesting variable continue var_comps = var.split('.') # container name is the 3rd after 'datalad'.'container'. cname = var_comps[2] if name and name != cname: # we are looking for a specific container's configuration # and this is not it continue # reconstruct config item name, anything after # datalad.containers.<name>. ccfgname = '.'.join(var_comps[3:]) if not ccfgname: continue if ccfgname == 'image': # run image path normalization to get a relative path # in platform conventions, regardless of the input. # for now we report a str, because the rest of the code # is not using pathlib value = str(_normalize_image_path(value, ds)) cinfo = containers.get(cname, {}) cinfo[ccfgname] = value containers[cname] = cinfo return containers if name is None else containers.get(name, {})
def _normalize_image_path(path: str, ds: Dataset) -> PurePath: """Helper to standardize container image path handling Previously, container configuration would contain platform-paths for container image location (e.g., windows paths when added on windows, POSIX paths elsewhere). This made cross-platform reuse impossible out-of-the box, but it also means that such dataset are out there in unknown numbers. This helper inspects an image path READ FROM CONFIG(!) and ensures that it matches platform conventions (because all other arguments) also come in platform conventions. This enables standardizing the storage conventions to be POSIX-only (for the future). Parameters ---------- path: str A str-path, as read from the configuration, matching its conventions (relative path, pointing to a container image relative to the dataset's root). ds: Dataset This dataset's base path is used as a reference for resolving the relative image path to an absolute location on the file system. Returns ------- PurePath Relative path in platform conventions """ # we only need to act differently, when an incoming path is # windows. This is not possible to say with 100% confidence, # because a POSIX path can also contain a backslash. We support # a few standard cases where we CAN tell pathobj = None if '\\' not in path: # no windows pathsep, no problem pathobj = PurePosixPath(path) elif path.startswith(r'.datalad\\environments\\'): # this is the default location setup in windows conventions pathobj = PureWindowsPath(path) else: # let's assume it is windows for a moment if lexists(str(ds.pathobj / PureWindowsPath(path))): # if there is something on the filesystem for this path, # we can be reasonably sure that this is indeed a windows # path. This won't catch images in uninstalled subdataset, # but better than nothing pathobj = PureWindowsPath(path) else: # if we get here, we have no idea, and no means to verify # further hypotheses -- go with the POSIX assumption # and hope for the best pathobj = PurePosixPath(path) assert pathobj is not None # we report in platform-conventions return PurePath(pathobj)