Source code for datalad_next.commands.results

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
import logging
from pathlib import (
    Path,
    PurePath,
)

from datalad_next.datasets import Dataset
from datalad_next.exceptions import CapturedException


# TODO Could be `StrEnum`, came with PY3.11
[docs] class CommandResultStatus(Enum): """Enumeration of possible statuses of command results """ ok = 'ok' notneeded = 'notneeded' impossible = 'impossible' error = 'error'
# which status is a success , which is failure success_status_map = { 'ok': 'success', 'notneeded': 'success', 'impossible': 'failure', 'error': 'failure', } # We really want this to be `kw_only=True`, but cannot, because it only # came with PY3.10 # Until this can be enabled, we cannot have additional _required_ properties # coming from derived classes. Instead, we have to make any and all # additional properties optional (with default None), because also in this # base class we do define optional ones (and it makes no sense not to do # that either). #@dataclass(kw_only=True)
[docs] @dataclass class CommandResult: """Base data class for result records emitted by DataLad commands. Historically, such results records have taken the form of a Python ``dict``. This class provides some API for its instances to be compatible with legacy code that expects a ``dict``. .. seealso:: https://docs.datalad.org/design/result_records.html """ # TODO implement post_init and possibly check for validated of # some arguments (e.g. status is a valid value). Maybe do all of that # conditional on some config flag that could be set during test # execution # mandatory as per # http://docs.datalad.org/design/result_records.html#mandatory-fields action: str """A string label identifying which type of operation a result is associated with. Labels must not contain white space. They should be compact, and lower-cases, and use ``_`` (underscore) to separate words in compound labels. """ status: CommandResultStatus """This field indicates the nature of a result in terms of four categories, identified by a :class:`CommandResultStatus` value. The result status is used by user communication, but also for decision making on the overall success or failure of a command operation. """ path: str | Path """An *absolute* path describing the local entity a result is associated with (the subject of the result record). Paths must be platform-specific (e.g., Windows paths on Windows, and POSIX paths on other operating systems). When a result is about an entity that has no meaningful relation to the local file system (e.g., a URL to be downloaded), the ``path`` value should be determined with respect to the potential impact of the result on any local entity (e.g., a URL downloaded to a local file path, a local dataset modified based on remote information). """ # optional # TODO complete documentation of all members message: str | tuple | None = None exception: CapturedException | None = None error_message: str | tuple | None = None type: str | None = None logger: logging.Logger | None = None refds: str | Path | Dataset = None # any and all of the code below makes it possible to feed such result # instances through the datalad-core result processing loop (which # expects results to be dicts with string keys and (most) values to # be string only also. def __contains__(self, key: str) -> bool: return hasattr(self, key) def __getitem__(self, key: str): return self._stringify4legacy(getattr(self, key))
[docs] def get(self, key, default=None): return self._stringify4legacy(getattr(self, key, default))
[docs] def pop(self, key, default=None): item = getattr(self, key, default) if hasattr(self, key): setattr(self, key, None) return self._stringify4legacy(item)
[docs] def items(self): for k, v in self.__dict__.items(): yield k, self._stringify4legacy(v)
def _stringify4legacy(self, val): if isinstance(val, PurePath): return str(val) elif isinstance(val, Dataset): return val.path elif issubclass(getattr(val, '__class__', None), Enum): return val.value return val