{
    "file_diffs": [
        {
            "old_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Helper for parameter validation, documentation and conversion\"\"\"\n\n__docformat__ = 'restructuredtext'\n\nimport re\n\n\ndef _strip_typerepr(s):\n    \"\"\"Strip away <class '...'> and <type '...'> decorations for docstrings\n    \"\"\"\n    return re.sub(r\"<(class|type) '(\\S+)'>\", r'\\2', s)\n\n\ndef _type_str(t):\n    \"\"\"Get string human-readable representation of a data type\n\n    If type (t) is given as a tuple, assume ability to choose any of the\n    listed types, so those types listing get joined with |\n    \"\"\"\n    if isinstance(t, tuple):\n        s = ' or '.join(map(_type_str, t))\n        return (\"(%s)\" % s) if len(t) > 1 else s\n    return _strip_typerepr(str(t))\n\n\nclass _NoneDeprecated:\n    \"\"\"A helper construct to ease migrations for option=None.\n\n    Should be instantiated, so it gains `__repr__` and thus would render nicely\n    in docs etc.\n    \"\"\"\n    def __repr__(self):\n        return 'None(DEPRECATED)'\n\n\nNoneDeprecated = _NoneDeprecated()\n\n\nclass Constraint(object):\n    \"\"\"Base class for input value conversion/validation.\n\n    These classes are also meant to be able to generate appropriate\n    documentation on an appropriate parameter value.\n    \"\"\"\n\n    # TODO: __str__ and/or __repr__ for every one of them\n\n    def __repr__(self):\n        \"\"\"Rudimentary repr to avoid default scary to the user Python repr\"\"\"\n        return \"constraint:%s\" % self.short_description()\n\n    def __and__(self, other):\n        return Constraints(self, other)\n\n    def __or__(self, other):\n        return AltConstraints(self, other)\n\n    def __call__(self, value):\n        # do any necessary checks or conversions, potentially catch exceptions\n        # and generate a meaningful error message\n        raise NotImplementedError(\"abstract class\")\n\n    def long_description(self):\n        # return meaningful docs or None\n        # used as a comprehensive description in the parameter list\n        return self.short_description()\n\n    def short_description(self):\n        # return meaningful docs or None\n        # used as a condensed primer for the parameter lists\n        raise NotImplementedError(\"abstract class\")\n\n\nclass EnsureDType(Constraint):\n    \"\"\"Ensure that an input (or several inputs) are of a particular data type.\n    \"\"\"\n    # TODO extend to support numpy-like dtype specs, e.g. 'int64'\n    # in addition to functors\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n\n    def __call__(self, value):\n        if hasattr(value, '__iter__') and \\\n                not (isinstance(value, (bytes, str))):\n            return list(map(self._dtype, value))\n        else:\n            return self._dtype(value)\n\n    def short_description(self):\n        return _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to type '%s'\" % self.short_description()\n\n\nclass EnsureInt(EnsureDType):\n    \"\"\"Ensure that an input (or several inputs) are of a data type 'int'.\n    \"\"\"\n    def __init__(self):\n        \"\"\"Initializes EnsureDType with int\"\"\"\n        EnsureDType.__init__(self, int)\n\n\nclass EnsureFloat(EnsureDType):\n    \"\"\"Ensure that an input (or several inputs) are of a data type 'float'.\n    \"\"\"\n    def __init__(self):\n        \"\"\"Initializes EnsureDType with float\"\"\"\n        EnsureDType.__init__(self, float)\n\n\nclass EnsureListOf(Constraint):\n    \"\"\"Ensure that an input is a list of a particular data type\n    \"\"\"\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n        super(EnsureListOf, self).__init__()\n\n    def __call__(self, value):\n        return list(map(self._dtype, value))\n\n    def short_description(self):\n        return 'list(%s)' % _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to %s\" % self.short_description()\n\n\nclass EnsureTupleOf(Constraint):\n    \"\"\"Ensure that an input is a tuple of a particular data type\n    \"\"\"\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n        super(EnsureTupleOf, self).__init__()\n\n    def __call__(self, value):\n        return tuple(map(self._dtype, value))\n\n    def short_description(self):\n        return 'tuple(%s)' % _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to %s\" % self.short_description()\n\n\nclass EnsureBool(Constraint):\n    \"\"\"Ensure that an input is a bool.\n\n    A couple of literal labels are supported, such as:\n    False: '0', 'no', 'off', 'disable', 'false'\n    True: '1', 'yes', 'on', 'enable', 'true'\n    \"\"\"\n    def __call__(self, value):\n        if isinstance(value, bool):\n            return value\n        elif isinstance(value, (bytes, str)):\n            value = value.lower()\n            if value in ('0', 'no', 'off', 'disable', 'false'):\n                return False\n            elif value in ('1', 'yes', 'on', 'enable', 'true'):\n                return True\n        raise ValueError(\n            \"value '{}' must be convertible to boolean\".format(\n                value))\n\n    def long_description(self):\n        return 'value must be convertible to type bool'\n\n    def short_description(self):\n        return 'bool'\n\n\nclass EnsureStr(Constraint):\n    \"\"\"Ensure an input is a string.\n\n    No automatic conversion is attempted.\n    \"\"\"\n    def __init__(self, min_len=0):\n        \"\"\"\n        Parameters\n        ----------\n        min_len: int, optional\n           Minimal length for a string.\n        \"\"\"\n        assert(min_len >= 0)\n        self._min_len = min_len\n        super(EnsureStr, self).__init__()\n\n    def __call__(self, value):\n        if not isinstance(value, (bytes, str)):\n            # do not perform a blind conversion ala str(), as almost\n            # anything can be converted and the result is most likely\n            # unintended\n            raise ValueError(\"%s is not a string\" % repr(value))\n        if len(value) < self._min_len:\n            raise ValueError(\"%r is shorter than of minimal length %d\"\n                             % (value, self._min_len))\n        return value\n\n    def long_description(self):\n        return 'value must be a string'\n\n    def short_description(self):\n        return 'str'\n\n\nclass EnsureStrPrefix(EnsureStr):\n    \"\"\"Ensure an input is a string that starts with a given prefix.\n    \"\"\"\n    def __init__(self, prefix):\n        \"\"\"\n        Parameters\n        ----------\n        prefix : str\n           Mandatory prefix.\n        \"\"\"\n        self._prefix = prefix\n        super().__init__()\n\n    def __call__(self, value):\n        super().__call__(value)\n        if not value.startswith(self._prefix):\n            raise ValueError(\"%r does not start with '%s'\"\n                             % (value, self._prefix))\n        return value\n\n    def long_description(self):\n        return \"value must start with '{}'\".format(self._prefix)\n\n    def short_description(self):\n        return '{}...'.format(self._prefix)\n\n\nclass EnsureNone(Constraint):\n    \"\"\"Ensure an input is of value `None`\"\"\"\n    def __call__(self, value):\n        if value is None or isinstance(value, _NoneDeprecated):\n            return None\n        else:\n            raise ValueError(\"value must be `None`\")\n\n    def short_description(self):\n        return 'None'\n\n    def long_description(self):\n        return 'value must be `None`'\n\n\nclass EnsureCallable(Constraint):\n    \"\"\"Ensure an input is a callable object\"\"\"\n    def __call__(self, value):\n        if hasattr(value, '__call__'):\n            return value\n        else:\n            raise ValueError(\"value must be a callable\")\n\n    def short_description(self):\n        return 'callable'\n\n    def long_description(self):\n        return 'value must be a callable'\n\n\nclass EnsureChoice(Constraint):\n    \"\"\"Ensure an input is element of a set of possible values\"\"\"\n\n    def __init__(self, *values):\n        \"\"\"\n        Parameters\n        ----------\n        *values\n           Possible accepted values.\n        \"\"\"\n        self._allowed = values\n        super(EnsureChoice, self).__init__()\n\n    def __call__(self, value):\n        if value not in self._allowed:\n            raise ValueError(f\"value {value} is not one of {self._allowed}\")\n        return value\n\n    def long_description(self):\n        return 'value must be one of [CMD: %s CMD][PY: %s PY]' % (\n            str(tuple(i for i in self._allowed if i is not None)),\n            str(self._allowed)\n        )\n\n    def short_description(self):\n        return '{%s}' % ', '.join([repr(c) for c in self._allowed])\n\n\nclass EnsureKeyChoice(EnsureChoice):\n    \"\"\"Ensure value under a key in an input is in a set of possible values\"\"\"\n\n    def __init__(self, key, values):\n        \"\"\"\n        Parameters\n        ----------\n        key : str\n          The to-be-tested values are looked up under the given key in\n          a dict-like input object.\n        values : tuple\n           Possible accepted values.\n        \"\"\"\n        self._key = key\n        super(EnsureKeyChoice, self).__init__(*values)\n\n    def __call__(self, value):\n        if self._key not in value:\n            raise ValueError(\"value not dict-like\")\n        super(EnsureKeyChoice, self).__call__(value[self._key])\n        return value\n\n    def long_description(self):\n        return \"value in '%s' must be one of %s\" % (self._key, str(self._allowed),)\n\n    def short_description(self):\n        return '%s:{%s}' % (self._key, ', '.join([str(c) for c in self._allowed]))\n\n\nclass EnsureRange(Constraint):\n    \"\"\"Ensure an input is within a particular range\n\n    No type checks are performed.\n    \"\"\"\n    def __init__(self, min=None, max=None):\n        \"\"\"\n        Parameters\n        ----------\n        min\n            Minimal value to be accepted in the range\n        max\n            Maximal value to be accepted in the range\n        \"\"\"\n        self._min = min\n        self._max = max\n        super(EnsureRange, self).__init__()\n\n    def __call__(self, value):\n        if self._min is not None:\n            if value < self._min:\n                raise ValueError(\"value must be at least %s\" % (self._min,))\n        if self._max is not None:\n            if value > self._max:\n                raise ValueError(\"value must be at most %s\" % (self._max,))\n        return value\n\n    def long_description(self):\n        min_str = '-inf' if self._min is None else str(self._min)\n        max_str = 'inf' if self._max is None else str(self._max)\n        return 'value must be in range [%s, %s]' % (min_str, max_str)\n\n    def short_description(self):\n        return None\n\n\nclass _MultiConstraint(Constraint):\n    \"\"\"Helper class to override the description methods to reported\n    multiple constraints\n    \"\"\"\n    def _get_description(self, attr):\n        cs = [\n            getattr(c, attr)()\n            for c in self.constraints\n            if hasattr(c, attr)\n        ]\n        cs = [c for c in cs if c is not None]\n        doc = ' or '.join(cs)\n        if len(cs) > 1:\n            return f'({doc})'\n        else:\n            return doc\n\n    def long_description(self):\n        return self._get_description('long_description')\n\n    def short_description(self):\n        return self._get_description('short_description')\n\n\nclass AltConstraints(_MultiConstraint):\n    \"\"\"Logical OR for constraints.\n\n    An arbitrary number of constraints can be given. They are evaluated in the\n    order in which they were specified. The value returned by the first\n    constraint that does not raise an exception is the global return value.\n\n    Documentation is aggregated for all alternative constraints.\n    \"\"\"\n    def __init__(self, *constraints):\n        \"\"\"\n        Parameters\n        ----------\n        *constraints\n           Alternative constraints\n        \"\"\"\n        super(AltConstraints, self).__init__()\n        self.constraints = [EnsureNone() if c is None else c for c in constraints]\n\n    def __or__(self, other):\n        if isinstance(other, AltConstraints):\n            self.constraints.extend(other.constraints)\n        else:\n            self.constraints.append(other)\n        return self\n\n    def __call__(self, value):\n        e_list = []\n        for c in self.constraints:\n            try:\n                return c(value)\n            except Exception as e:\n                e_list.append(e)\n        raise ValueError(f\"{value} does not match any alternative: \"\n                         f\"{self.constraints} {e_list}\")\n\n\nclass Constraints(_MultiConstraint):\n    \"\"\"Logical AND for constraints.\n\n    An arbitrary number of constraints can be given. They are evaluated in the\n    order in which they were specified. The return value of each constraint is\n    passed an input into the next. The return value of the last constraint\n    is the global return value. No intermediate exceptions are caught.\n\n    Documentation is aggregated for all constraints.\n    \"\"\"\n    def __init__(self, *constraints):\n        \"\"\"\n        Parameters\n        ----------\n        *constraints\n           Constraints all of which must be satisfied\n        \"\"\"\n        super(Constraints, self).__init__()\n        self.constraints = [EnsureNone() if c is None else c for c in constraints]\n\n    def __and__(self, other):\n        if isinstance(other, Constraints):\n            self.constraints.extend(other.constraints)\n        else:\n            self.constraints.append(other)\n        return self\n\n    def __call__(self, value):\n        for c in (self.constraints):\n            value = c(value)\n        return value\n\n\nconstraint_spec_map = {\n    'float': EnsureFloat(),\n    'int': EnsureInt(),\n    'bool': EnsureBool(),\n    'str': EnsureStr(),\n}\n\n\ndef expand_constraint_spec(spec):\n    \"\"\"Helper to translate literal constraint specs into functional ones\n\n    e.g. 'float' -> EnsureFloat()\n    \"\"\"\n    if spec is None or hasattr(spec, '__call__'):\n        return spec\n    else:\n        try:\n            return constraint_spec_map[spec]\n        except KeyError:\n            raise ValueError(\"unsupported constraint specification '%r'\" % (spec,))\n",
            "new_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Helper for parameter validation, documentation and conversion\"\"\"\n\n__docformat__ = 'restructuredtext'\n\nimport re\n\n\ndef _strip_typerepr(s):\n    \"\"\"Strip away <class '...'> and <type '...'> decorations for docstrings\n    \"\"\"\n    return re.sub(r\"<(class|type) '(\\S+)'>\", r'\\2', s)\n\n\ndef _type_str(t):\n    \"\"\"Get string human-readable representation of a data type\n\n    If type (t) is given as a tuple, assume ability to choose any of the\n    listed types, so those types listing get joined with |\n    \"\"\"\n    if isinstance(t, tuple):\n        s = ' or '.join(map(_type_str, t))\n        return (\"(%s)\" % s) if len(t) > 1 else s\n    return _strip_typerepr(str(t))\n\n\nclass _NoneDeprecated:\n    \"\"\"A helper construct to ease migrations for option=None.\n\n    Should be instantiated, so it gains `__repr__` and thus would render nicely\n    in docs etc.\n    \"\"\"\n    def __repr__(self):\n        return 'None(DEPRECATED)'\n\n\nNoneDeprecated = _NoneDeprecated()\n\n\nclass Constraint(object):\n    \"\"\"Base class for input value conversion/validation.\n\n    These classes are also meant to be able to generate appropriate\n    documentation on an appropriate parameter value.\n    \"\"\"\n\n    # TODO: __str__ and/or __repr__ for every one of them\n\n    def __repr__(self):\n        \"\"\"Rudimentary repr to avoid default scary to the user Python repr\"\"\"\n        return \"constraint:%s\" % self.short_description()\n\n    def __and__(self, other):\n        return Constraints(self, other)\n\n    def __or__(self, other):\n        return AltConstraints(self, other)\n\n    def __call__(self, value):\n        # do any necessary checks or conversions, potentially catch exceptions\n        # and generate a meaningful error message\n        raise NotImplementedError(\"abstract class\")\n\n    def long_description(self):\n        # return meaningful docs or None\n        # used as a comprehensive description in the parameter list\n        return self.short_description()\n\n    def short_description(self):\n        # return meaningful docs or None\n        # used as a condensed primer for the parameter lists\n        raise NotImplementedError(\"abstract class\")\n\n\nclass EnsureDType(Constraint):\n    \"\"\"Ensure that an input (or several inputs) are of a particular data type.\n    \"\"\"\n    # TODO extend to support numpy-like dtype specs, e.g. 'int64'\n    # in addition to functors\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n\n    def __call__(self, value):\n        if hasattr(value, '__iter__') and \\\n                not (isinstance(value, (bytes, str))):\n            return list(map(self._dtype, value))\n        else:\n            return self._dtype(value)\n\n    def short_description(self):\n        return _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to type '%s'\" % self.short_description()\n\n\nclass EnsureInt(EnsureDType):\n    \"\"\"Ensure that an input (or several inputs) are of a data type 'int'.\n    \"\"\"\n    def __init__(self):\n        \"\"\"Initializes EnsureDType with int\"\"\"\n        EnsureDType.__init__(self, int)\n\n\nclass EnsureFloat(EnsureDType):\n    \"\"\"Ensure that an input (or several inputs) are of a data type 'float'.\n    \"\"\"\n    def __init__(self):\n        \"\"\"Initializes EnsureDType with float\"\"\"\n        EnsureDType.__init__(self, float)\n\n\nclass EnsureListOf(Constraint):\n    \"\"\"Ensure that an input is a list of a particular data type\n    \"\"\"\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n        super(EnsureListOf, self).__init__()\n\n    def __call__(self, value):\n        return list(map(self._dtype, ([value] if isinstance(value, str) else value)))\n\n    def short_description(self):\n        return 'list(%s)' % _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to %s\" % self.short_description()\n\n\nclass EnsureTupleOf(Constraint):\n    \"\"\"Ensure that an input is a tuple of a particular data type\n    \"\"\"\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n        super(EnsureTupleOf, self).__init__()\n\n    def __call__(self, value):\n        return tuple(map(self._dtype, ([value] if isinstance(value, str) else value)))\n\n    def short_description(self):\n        return 'tuple(%s)' % _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to %s\" % self.short_description()\n\n\nclass EnsureBool(Constraint):\n    \"\"\"Ensure that an input is a bool.\n\n    A couple of literal labels are supported, such as:\n    False: '0', 'no', 'off', 'disable', 'false'\n    True: '1', 'yes', 'on', 'enable', 'true'\n    \"\"\"\n    def __call__(self, value):\n        if isinstance(value, bool):\n            return value\n        elif isinstance(value, (bytes, str)):\n            value = value.lower()\n            if value in ('0', 'no', 'off', 'disable', 'false'):\n                return False\n            elif value in ('1', 'yes', 'on', 'enable', 'true'):\n                return True\n        raise ValueError(\n            \"value '{}' must be convertible to boolean\".format(\n                value))\n\n    def long_description(self):\n        return 'value must be convertible to type bool'\n\n    def short_description(self):\n        return 'bool'\n\n\nclass EnsureStr(Constraint):\n    \"\"\"Ensure an input is a string.\n\n    No automatic conversion is attempted.\n    \"\"\"\n    def __init__(self, min_len=0):\n        \"\"\"\n        Parameters\n        ----------\n        min_len: int, optional\n           Minimal length for a string.\n        \"\"\"\n        assert(min_len >= 0)\n        self._min_len = min_len\n        super(EnsureStr, self).__init__()\n\n    def __call__(self, value):\n        if not isinstance(value, (bytes, str)):\n            # do not perform a blind conversion ala str(), as almost\n            # anything can be converted and the result is most likely\n            # unintended\n            raise ValueError(\"%s is not a string\" % repr(value))\n        if len(value) < self._min_len:\n            raise ValueError(\"%r is shorter than of minimal length %d\"\n                             % (value, self._min_len))\n        return value\n\n    def long_description(self):\n        return 'value must be a string'\n\n    def short_description(self):\n        return 'str'\n\n\nclass EnsureStrPrefix(EnsureStr):\n    \"\"\"Ensure an input is a string that starts with a given prefix.\n    \"\"\"\n    def __init__(self, prefix):\n        \"\"\"\n        Parameters\n        ----------\n        prefix : str\n           Mandatory prefix.\n        \"\"\"\n        self._prefix = prefix\n        super().__init__()\n\n    def __call__(self, value):\n        super().__call__(value)\n        if not value.startswith(self._prefix):\n            raise ValueError(\"%r does not start with '%s'\"\n                             % (value, self._prefix))\n        return value\n\n    def long_description(self):\n        return \"value must start with '{}'\".format(self._prefix)\n\n    def short_description(self):\n        return '{}...'.format(self._prefix)\n\n\nclass EnsureNone(Constraint):\n    \"\"\"Ensure an input is of value `None`\"\"\"\n    def __call__(self, value):\n        if value is None or isinstance(value, _NoneDeprecated):\n            return None\n        else:\n            raise ValueError(\"value must be `None`\")\n\n    def short_description(self):\n        return 'None'\n\n    def long_description(self):\n        return 'value must be `None`'\n\n\nclass EnsureCallable(Constraint):\n    \"\"\"Ensure an input is a callable object\"\"\"\n    def __call__(self, value):\n        if hasattr(value, '__call__'):\n            return value\n        else:\n            raise ValueError(\"value must be a callable\")\n\n    def short_description(self):\n        return 'callable'\n\n    def long_description(self):\n        return 'value must be a callable'\n\n\nclass EnsureChoice(Constraint):\n    \"\"\"Ensure an input is element of a set of possible values\"\"\"\n\n    def __init__(self, *values):\n        \"\"\"\n        Parameters\n        ----------\n        *values\n           Possible accepted values.\n        \"\"\"\n        self._allowed = values\n        super(EnsureChoice, self).__init__()\n\n    def __call__(self, value):\n        if value not in self._allowed:\n            raise ValueError(f\"value {value} is not one of {self._allowed}\")\n        return value\n\n    def long_description(self):\n        return 'value must be one of [CMD: %s CMD][PY: %s PY]' % (\n            str(tuple(i for i in self._allowed if i is not None)),\n            str(self._allowed)\n        )\n\n    def short_description(self):\n        return '{%s}' % ', '.join([repr(c) for c in self._allowed])\n\n\nclass EnsureKeyChoice(EnsureChoice):\n    \"\"\"Ensure value under a key in an input is in a set of possible values\"\"\"\n\n    def __init__(self, key, values):\n        \"\"\"\n        Parameters\n        ----------\n        key : str\n          The to-be-tested values are looked up under the given key in\n          a dict-like input object.\n        values : tuple\n           Possible accepted values.\n        \"\"\"\n        self._key = key\n        super(EnsureKeyChoice, self).__init__(*values)\n\n    def __call__(self, value):\n        if self._key not in value:\n            raise ValueError(\"value not dict-like\")\n        super(EnsureKeyChoice, self).__call__(value[self._key])\n        return value\n\n    def long_description(self):\n        return \"value in '%s' must be one of %s\" % (self._key, str(self._allowed),)\n\n    def short_description(self):\n        return '%s:{%s}' % (self._key, ', '.join([str(c) for c in self._allowed]))\n\n\nclass EnsureRange(Constraint):\n    \"\"\"Ensure an input is within a particular range\n\n    No type checks are performed.\n    \"\"\"\n    def __init__(self, min=None, max=None):\n        \"\"\"\n        Parameters\n        ----------\n        min\n            Minimal value to be accepted in the range\n        max\n            Maximal value to be accepted in the range\n        \"\"\"\n        self._min = min\n        self._max = max\n        super(EnsureRange, self).__init__()\n\n    def __call__(self, value):\n        if self._min is not None:\n            if value < self._min:\n                raise ValueError(\"value must be at least %s\" % (self._min,))\n        if self._max is not None:\n            if value > self._max:\n                raise ValueError(\"value must be at most %s\" % (self._max,))\n        return value\n\n    def long_description(self):\n        min_str = '-inf' if self._min is None else str(self._min)\n        max_str = 'inf' if self._max is None else str(self._max)\n        return 'value must be in range [%s, %s]' % (min_str, max_str)\n\n    def short_description(self):\n        return None\n\n\nclass _MultiConstraint(Constraint):\n    \"\"\"Helper class to override the description methods to reported\n    multiple constraints\n    \"\"\"\n    def _get_description(self, attr):\n        cs = [\n            getattr(c, attr)()\n            for c in self.constraints\n            if hasattr(c, attr)\n        ]\n        cs = [c for c in cs if c is not None]\n        doc = ' or '.join(cs)\n        if len(cs) > 1:\n            return f'({doc})'\n        else:\n            return doc\n\n    def long_description(self):\n        return self._get_description('long_description')\n\n    def short_description(self):\n        return self._get_description('short_description')\n\n\nclass AltConstraints(_MultiConstraint):\n    \"\"\"Logical OR for constraints.\n\n    An arbitrary number of constraints can be given. They are evaluated in the\n    order in which they were specified. The value returned by the first\n    constraint that does not raise an exception is the global return value.\n\n    Documentation is aggregated for all alternative constraints.\n    \"\"\"\n    def __init__(self, *constraints):\n        \"\"\"\n        Parameters\n        ----------\n        *constraints\n           Alternative constraints\n        \"\"\"\n        super(AltConstraints, self).__init__()\n        self.constraints = [EnsureNone() if c is None else c for c in constraints]\n\n    def __or__(self, other):\n        if isinstance(other, AltConstraints):\n            self.constraints.extend(other.constraints)\n        else:\n            self.constraints.append(other)\n        return self\n\n    def __call__(self, value):\n        e_list = []\n        for c in self.constraints:\n            try:\n                return c(value)\n            except Exception as e:\n                e_list.append(e)\n        raise ValueError(f\"{value} does not match any alternative: \"\n                         f\"{self.constraints} {e_list}\")\n\n\nclass Constraints(_MultiConstraint):\n    \"\"\"Logical AND for constraints.\n\n    An arbitrary number of constraints can be given. They are evaluated in the\n    order in which they were specified. The return value of each constraint is\n    passed an input into the next. The return value of the last constraint\n    is the global return value. No intermediate exceptions are caught.\n\n    Documentation is aggregated for all constraints.\n    \"\"\"\n    def __init__(self, *constraints):\n        \"\"\"\n        Parameters\n        ----------\n        *constraints\n           Constraints all of which must be satisfied\n        \"\"\"\n        super(Constraints, self).__init__()\n        self.constraints = [EnsureNone() if c is None else c for c in constraints]\n\n    def __and__(self, other):\n        if isinstance(other, Constraints):\n            self.constraints.extend(other.constraints)\n        else:\n            self.constraints.append(other)\n        return self\n\n    def __call__(self, value):\n        for c in (self.constraints):\n            value = c(value)\n        return value\n\n\nconstraint_spec_map = {\n    'float': EnsureFloat(),\n    'int': EnsureInt(),\n    'bool': EnsureBool(),\n    'str': EnsureStr(),\n}\n\n\ndef expand_constraint_spec(spec):\n    \"\"\"Helper to translate literal constraint specs into functional ones\n\n    e.g. 'float' -> EnsureFloat()\n    \"\"\"\n    if spec is None or hasattr(spec, '__call__'):\n        return spec\n    else:\n        try:\n            return constraint_spec_map[spec]\n        except KeyError:\n            raise ValueError(\"unsupported constraint specification '%r'\" % (spec,))\n",
            "header": {
                "file": {
                    "path": "datalad/support/constraints.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "0f7533485",
                "new_commit_hash": "623fab363",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/support/constraints.py"
            },
            "plus_file": {
                "path": "b/datalad/support/constraints.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 135,
                            "length": 7
                        },
                        "new_range": {
                            "start": 135,
                            "length": 7
                        },
                        "section": "class EnsureListOf(Constraint):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        super(EnsureListOf, self).__init__()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def __call__(self, value):",
                                "type": "context"
                            },
                            {
                                "content": "        return list(map(self._dtype, value))",
                                "type": "deleted"
                            },
                            {
                                "content": "        return list(map(self._dtype, ([value] if isinstance(value, str) else value)))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def short_description(self):",
                                "type": "context"
                            },
                            {
                                "content": "        return 'list(%s)' % _type_str(self._dtype)",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/support/constraints.py",
                            "type": "class",
                            "name": "EnsureListOf",
                            "content": "class EnsureListOf(Constraint):\n    \"\"\"Ensure that an input is a list of a particular data type\n    \"\"\"\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n        super(EnsureListOf, self).__init__()\n\n    def __call__(self, value):\n        return list(map(self._dtype, value))\n\n    def short_description(self):\n        return 'list(%s)' % _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to %s\" % self.short_description()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 125,
                            "end_lineno": 144,
                            "parent": null
                        },
                        {
                            "file_name": "datalad/support/constraints.py",
                            "type": "method",
                            "name": "EnsureListOf.__call__",
                            "content": "class EnsureListOf(Constraint):\n    \"\"\"Ensure that an input is a list of a particular data type\n    \"\"\"\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n        super(EnsureListOf, self).__init__()\n\n    def __call__(self, value):\n        return list(map(self._dtype, value))\n\n    def short_description(self):\n        return 'list(%s)' % _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to %s\" % self.short_description()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 137,
                            "end_lineno": 138,
                            "parent": {
                                "file_name": "datalad/support/constraints.py",
                                "type": "class",
                                "name": "EnsureListOf",
                                "content": "class EnsureListOf(Constraint):\n    \"\"\"Ensure that an input is a list of a particular data type\n    \"\"\"\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n        super(EnsureListOf, self).__init__()\n\n    def __call__(self, value):\n        return list(map(self._dtype, value))\n\n    def short_description(self):\n        return 'list(%s)' % _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to %s\" % self.short_description()",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 125,
                                "end_lineno": 144,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 157,
                            "length": 7
                        },
                        "new_range": {
                            "start": 157,
                            "length": 7
                        },
                        "section": "class EnsureTupleOf(Constraint):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        super(EnsureTupleOf, self).__init__()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def __call__(self, value):",
                                "type": "context"
                            },
                            {
                                "content": "        return tuple(map(self._dtype, value))",
                                "type": "deleted"
                            },
                            {
                                "content": "        return tuple(map(self._dtype, ([value] if isinstance(value, str) else value)))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def short_description(self):",
                                "type": "context"
                            },
                            {
                                "content": "        return 'tuple(%s)' % _type_str(self._dtype)",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/support/constraints.py",
                            "type": "method",
                            "name": "EnsureTupleOf.__call__",
                            "content": "class EnsureTupleOf(Constraint):\n    \"\"\"Ensure that an input is a tuple of a particular data type\n    \"\"\"\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n        super(EnsureTupleOf, self).__init__()\n\n    def __call__(self, value):\n        return tuple(map(self._dtype, value))\n\n    def short_description(self):\n        return 'tuple(%s)' % _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to %s\" % self.short_description()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 159,
                            "end_lineno": 160,
                            "parent": {
                                "file_name": "datalad/support/constraints.py",
                                "type": "class",
                                "name": "EnsureTupleOf",
                                "content": "class EnsureTupleOf(Constraint):\n    \"\"\"Ensure that an input is a tuple of a particular data type\n    \"\"\"\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n        super(EnsureTupleOf, self).__init__()\n\n    def __call__(self, value):\n        return tuple(map(self._dtype, value))\n\n    def short_description(self):\n        return 'tuple(%s)' % _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to %s\" % self.short_description()",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 147,
                                "end_lineno": 166,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "datalad/support/constraints.py",
                            "type": "class",
                            "name": "EnsureTupleOf",
                            "content": "class EnsureTupleOf(Constraint):\n    \"\"\"Ensure that an input is a tuple of a particular data type\n    \"\"\"\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n        super(EnsureTupleOf, self).__init__()\n\n    def __call__(self, value):\n        return tuple(map(self._dtype, value))\n\n    def short_description(self):\n        return 'tuple(%s)' % _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to %s\" % self.short_description()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 147,
                            "end_lineno": 166,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-\n# vi: set ft=python sts=4 ts=4 sw=4 et:\n### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the DataLad package for the\n#   copyright and license terms.\n#\n### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n'''Unit tests for basic constraints functionality.'''\n\n\nfrom datalad.tests.utils_pytest import (\n    assert_equal,\n    assert_raises,\n)\n\nfrom ..support import constraints as ct\n\n\ndef test_int():\n    c = ct.EnsureInt()\n    # this should always work\n    assert_equal(c(7), 7)\n    assert_equal(c(7.0), 7)\n    assert_equal(c('7'), 7)\n    assert_equal(c([7, 3]), [7, 3])\n    # this should always fail\n    assert_raises(ValueError, lambda: c('fail'))\n    assert_raises(ValueError, lambda: c([3, 'fail']))\n    # this will also fail\n    assert_raises(ValueError, lambda: c('17.0'))\n    assert_equal(c.short_description(), 'int')\n\n\ndef test_float():\n    c = ct.EnsureFloat()\n    # this should always work\n    assert_equal(c(7.0), 7.0)\n    assert_equal(c(7), 7.0)\n    assert_equal(c('7'), 7.0)\n    assert_equal(c([7.0, '3.0']), [7.0, 3.0])\n    # this should always fail\n    assert_raises(ValueError, lambda: c('fail'))\n    assert_raises(ValueError, lambda: c([3.0, 'fail']))\n\n\ndef test_bool():\n    c = ct.EnsureBool()\n    # this should always work\n    assert_equal(c(True), True)\n    assert_equal(c(False), False)\n    # all that resuls in True\n    assert_equal(c('True'), True)\n    assert_equal(c('true'), True)\n    assert_equal(c('1'), True)\n    assert_equal(c('yes'), True)\n    assert_equal(c('on'), True)\n    assert_equal(c('enable'), True)\n    # all that resuls in False\n    assert_equal(c('false'), False)\n    assert_equal(c('False'), False)\n    assert_equal(c('0'), False)\n    assert_equal(c('no'), False)\n    assert_equal(c('off'), False)\n    assert_equal(c('disable'), False)\n    # this should always fail\n    assert_raises(ValueError, c, 0)\n    assert_raises(ValueError, c, 1)\n\n\ndef test_str():\n    c = ct.EnsureStr()\n    # this should always work\n    assert_equal(c('hello'), 'hello')\n    assert_equal(c('7.0'), '7.0')\n    # this should always fail\n    assert_raises(ValueError, lambda: c(['ab']))\n    assert_raises(ValueError, lambda: c(['a', 'b']))\n    assert_raises(ValueError, lambda: c(('a', 'b')))\n    # no automatic conversion attempted\n    assert_raises(ValueError, lambda: c(7.0))\n    assert_equal(c.short_description(), 'str')\n\ndef test_str_min_len():\n    c = ct.EnsureStr(min_len=1)\n    assert_equal(c('hello'), 'hello')\n    assert_equal(c('h'), 'h')\n    assert_raises(ValueError, c, '')\n\n    c = ct.EnsureStr(min_len=2)\n    assert_equal(c('hello'), 'hello')\n    assert_raises(ValueError, c, 'h')\n\n\ndef test_none():\n    c = ct.EnsureNone()\n    # this should always work\n    assert_equal(c(None), None)\n    # instance of NoneDeprecated is also None\n    assert_equal(c(ct.NoneDeprecated), None)\n    # this should always fail\n    assert_raises(ValueError, lambda: c('None'))\n    assert_raises(ValueError, lambda: c([]))\n\n\ndef test_callable():\n    c = ct.EnsureCallable()\n    # this should always work\n    assert_equal(c(range), range)\n    assert_raises(ValueError, c, 'range')\n\n\ndef test_choice():\n    c = ct.EnsureChoice('choice1', 'choice2', None)\n    # this should always work\n    assert_equal(c('choice1'), 'choice1')\n    assert_equal(c(None), None)\n    # this should always fail\n    assert_raises(ValueError, lambda: c('fail'))\n    assert_raises(ValueError, lambda: c('None'))\n\n\ndef test_keychoice():\n    c = ct.EnsureKeyChoice(key='some', values=('choice1', 'choice2', None))\n    assert_equal(c({'some': 'choice1'}), {'some': 'choice1'})\n    assert_equal(c({'some': None}), {'some': None})\n    assert_equal(c({'some': None, 'ign': 'ore'}), {'some': None, 'ign': 'ore'})\n    assert_raises(ValueError, c, 'fail')\n    assert_raises(ValueError, c, 'None')\n    assert_raises(ValueError, c, {'nope': 'None'})\n    assert_raises(ValueError, c, {'some': 'None'})\n    assert_raises(ValueError, c, {'some': ('a', 'b')})\n\n\ndef test_range():\n    c = ct.EnsureRange(min=3, max=7)\n    # this should always work\n    assert_equal(c(3.0), 3.0)\n\n    # this should always fail\n    assert_raises(ValueError, lambda: c(2.9999999))\n    assert_raises(ValueError, lambda: c(77))\n    assert_raises(TypeError, lambda: c('fail'))\n    assert_raises(TypeError, lambda: c((3, 4)))\n    # since no type checks are performed\n    assert_raises(TypeError, lambda: c('7'))\n\n    # Range doesn't have to be numeric\n    c = ct.EnsureRange(min=\"e\", max=\"qqq\")\n    assert_equal(c('e'), 'e')\n    assert_equal(c('fa'), 'fa')\n    assert_equal(c('qq'), 'qq')\n    assert_raises(ValueError, c, 'a')\n    assert_raises(ValueError, c, 'qqqa')\n\n\ndef test_listof():\n    c = ct.EnsureListOf(str)\n    assert_equal(c(['a', 'b']), ['a', 'b'])\n    assert_equal(c(['a1', 'b2']), ['a1', 'b2'])\n\n\ndef test_tupleof():\n    c = ct.EnsureTupleOf(str)\n    assert_equal(c(('a', 'b')), ('a', 'b'))\n    assert_equal(c(('a1', 'b2')), ('a1', 'b2'))\n\n\ndef test_constraints():\n    # this should always work\n    c = ct.Constraints(ct.EnsureFloat())\n    assert_equal(c(7.0), 7.0)\n    c = ct.Constraints(ct.EnsureFloat(), ct.EnsureRange(min=4.0))\n    assert_equal(c(7.0), 7.0)\n    # __and__ form\n    c = ct.EnsureFloat() & ct.EnsureRange(min=4.0)\n    assert_equal(c(7.0), 7.0)\n    assert_raises(ValueError, c, 3.9)\n    c = ct.Constraints(ct.EnsureFloat(), ct.EnsureRange(min=4), ct.EnsureRange(max=9))\n    assert_equal(c(7.0), 7.0)\n    assert_raises(ValueError, c, 3.9)\n    assert_raises(ValueError, c, 9.01)\n    # __and__ form\n    c = ct.EnsureFloat() & ct.EnsureRange(min=4) & ct.EnsureRange(max=9)\n    assert_equal(c(7.0), 7.0)\n    assert_raises(ValueError, c, 3.99)\n    assert_raises(ValueError, c, 9.01)\n    # and reordering should not have any effect\n    c = ct.Constraints(ct.EnsureRange(max=4), ct.EnsureRange(min=9), ct.EnsureFloat())\n    assert_raises(ValueError, c, 3.99)\n    assert_raises(ValueError, c, 9.01)\n\n\ndef test_altconstraints():\n    # this should always work\n    c = ct.AltConstraints(ct.EnsureFloat())\n    assert_equal(c(7.0), 7.0)\n    c = ct.AltConstraints(ct.EnsureFloat(), ct.EnsureNone())\n    assert_equal(c.short_description(), '(float or None)')\n    assert_equal(c(7.0), 7.0)\n    assert_equal(c(None), None)\n    # __or__ form\n    c = ct.EnsureFloat() | ct.EnsureNone()\n    assert_equal(c(7.0), 7.0)\n    assert_equal(c(None), None)\n\n    # this should always fail\n    c = ct.Constraints(ct.EnsureRange(min=0, max=4), ct.EnsureRange(min=9, max=11))\n    assert_raises(ValueError, c, 7.0)\n    c = ct.EnsureRange(min=0, max=4) | ct.EnsureRange(min=9, max=11)\n    assert_equal(c(3.0), 3.0)\n    assert_equal(c(9.0), 9.0)\n    assert_raises(ValueError, c, 7.0)\n    assert_raises(ValueError, c, -1.0)\n\n\ndef test_both():\n    # this should always work\n    c = ct.AltConstraints(\n        ct.Constraints(\n            ct.EnsureFloat(),\n            ct.EnsureRange(min=7.0, max=44.0)),\n        ct.EnsureNone())\n    assert_equal(c(7.0), 7.0)\n    assert_equal(c(None), None)\n    # this should always fail\n    assert_raises(ValueError, lambda: c(77.0))\n\ndef test_type_str():\n    assert_equal(ct._type_str((str,)), 'str')\n    assert_equal(ct._type_str(str), 'str')\n",
            "new_file_content": "# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-\n# vi: set ft=python sts=4 ts=4 sw=4 et:\n### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the DataLad package for the\n#   copyright and license terms.\n#\n### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n'''Unit tests for basic constraints functionality.'''\n\n\nfrom datalad.tests.utils_pytest import (\n    assert_equal,\n    assert_raises,\n)\n\nfrom ..support import constraints as ct\n\n\ndef test_int():\n    c = ct.EnsureInt()\n    # this should always work\n    assert_equal(c(7), 7)\n    assert_equal(c(7.0), 7)\n    assert_equal(c('7'), 7)\n    assert_equal(c([7, 3]), [7, 3])\n    # this should always fail\n    assert_raises(ValueError, lambda: c('fail'))\n    assert_raises(ValueError, lambda: c([3, 'fail']))\n    # this will also fail\n    assert_raises(ValueError, lambda: c('17.0'))\n    assert_equal(c.short_description(), 'int')\n\n\ndef test_float():\n    c = ct.EnsureFloat()\n    # this should always work\n    assert_equal(c(7.0), 7.0)\n    assert_equal(c(7), 7.0)\n    assert_equal(c('7'), 7.0)\n    assert_equal(c([7.0, '3.0']), [7.0, 3.0])\n    # this should always fail\n    assert_raises(ValueError, lambda: c('fail'))\n    assert_raises(ValueError, lambda: c([3.0, 'fail']))\n\n\ndef test_bool():\n    c = ct.EnsureBool()\n    # this should always work\n    assert_equal(c(True), True)\n    assert_equal(c(False), False)\n    # all that resuls in True\n    assert_equal(c('True'), True)\n    assert_equal(c('true'), True)\n    assert_equal(c('1'), True)\n    assert_equal(c('yes'), True)\n    assert_equal(c('on'), True)\n    assert_equal(c('enable'), True)\n    # all that resuls in False\n    assert_equal(c('false'), False)\n    assert_equal(c('False'), False)\n    assert_equal(c('0'), False)\n    assert_equal(c('no'), False)\n    assert_equal(c('off'), False)\n    assert_equal(c('disable'), False)\n    # this should always fail\n    assert_raises(ValueError, c, 0)\n    assert_raises(ValueError, c, 1)\n\n\ndef test_str():\n    c = ct.EnsureStr()\n    # this should always work\n    assert_equal(c('hello'), 'hello')\n    assert_equal(c('7.0'), '7.0')\n    # this should always fail\n    assert_raises(ValueError, lambda: c(['ab']))\n    assert_raises(ValueError, lambda: c(['a', 'b']))\n    assert_raises(ValueError, lambda: c(('a', 'b')))\n    # no automatic conversion attempted\n    assert_raises(ValueError, lambda: c(7.0))\n    assert_equal(c.short_description(), 'str')\n\ndef test_str_min_len():\n    c = ct.EnsureStr(min_len=1)\n    assert_equal(c('hello'), 'hello')\n    assert_equal(c('h'), 'h')\n    assert_raises(ValueError, c, '')\n\n    c = ct.EnsureStr(min_len=2)\n    assert_equal(c('hello'), 'hello')\n    assert_raises(ValueError, c, 'h')\n\n\ndef test_none():\n    c = ct.EnsureNone()\n    # this should always work\n    assert_equal(c(None), None)\n    # instance of NoneDeprecated is also None\n    assert_equal(c(ct.NoneDeprecated), None)\n    # this should always fail\n    assert_raises(ValueError, lambda: c('None'))\n    assert_raises(ValueError, lambda: c([]))\n\n\ndef test_callable():\n    c = ct.EnsureCallable()\n    # this should always work\n    assert_equal(c(range), range)\n    assert_raises(ValueError, c, 'range')\n\n\ndef test_choice():\n    c = ct.EnsureChoice('choice1', 'choice2', None)\n    # this should always work\n    assert_equal(c('choice1'), 'choice1')\n    assert_equal(c(None), None)\n    # this should always fail\n    assert_raises(ValueError, lambda: c('fail'))\n    assert_raises(ValueError, lambda: c('None'))\n\n\ndef test_keychoice():\n    c = ct.EnsureKeyChoice(key='some', values=('choice1', 'choice2', None))\n    assert_equal(c({'some': 'choice1'}), {'some': 'choice1'})\n    assert_equal(c({'some': None}), {'some': None})\n    assert_equal(c({'some': None, 'ign': 'ore'}), {'some': None, 'ign': 'ore'})\n    assert_raises(ValueError, c, 'fail')\n    assert_raises(ValueError, c, 'None')\n    assert_raises(ValueError, c, {'nope': 'None'})\n    assert_raises(ValueError, c, {'some': 'None'})\n    assert_raises(ValueError, c, {'some': ('a', 'b')})\n\n\ndef test_range():\n    c = ct.EnsureRange(min=3, max=7)\n    # this should always work\n    assert_equal(c(3.0), 3.0)\n\n    # this should always fail\n    assert_raises(ValueError, lambda: c(2.9999999))\n    assert_raises(ValueError, lambda: c(77))\n    assert_raises(TypeError, lambda: c('fail'))\n    assert_raises(TypeError, lambda: c((3, 4)))\n    # since no type checks are performed\n    assert_raises(TypeError, lambda: c('7'))\n\n    # Range doesn't have to be numeric\n    c = ct.EnsureRange(min=\"e\", max=\"qqq\")\n    assert_equal(c('e'), 'e')\n    assert_equal(c('fa'), 'fa')\n    assert_equal(c('qq'), 'qq')\n    assert_raises(ValueError, c, 'a')\n    assert_raises(ValueError, c, 'qqqa')\n\n\ndef test_listof():\n    c = ct.EnsureListOf(str)\n    assert_equal(c(['a', 'b']), ['a', 'b'])\n    assert_equal(c(['a1', 'b2']), ['a1', 'b2'])\n    assert_equal(c('a1 b2'), ['a1 b2'])\n\n\ndef test_tupleof():\n    c = ct.EnsureTupleOf(str)\n    assert_equal(c(('a', 'b')), ('a', 'b'))\n    assert_equal(c(('a1', 'b2')), ('a1', 'b2'))\n    assert_equal(c('a1 b2'), ('a1 b2',))\n\n\ndef test_constraints():\n    # this should always work\n    c = ct.Constraints(ct.EnsureFloat())\n    assert_equal(c(7.0), 7.0)\n    c = ct.Constraints(ct.EnsureFloat(), ct.EnsureRange(min=4.0))\n    assert_equal(c(7.0), 7.0)\n    # __and__ form\n    c = ct.EnsureFloat() & ct.EnsureRange(min=4.0)\n    assert_equal(c(7.0), 7.0)\n    assert_raises(ValueError, c, 3.9)\n    c = ct.Constraints(ct.EnsureFloat(), ct.EnsureRange(min=4), ct.EnsureRange(max=9))\n    assert_equal(c(7.0), 7.0)\n    assert_raises(ValueError, c, 3.9)\n    assert_raises(ValueError, c, 9.01)\n    # __and__ form\n    c = ct.EnsureFloat() & ct.EnsureRange(min=4) & ct.EnsureRange(max=9)\n    assert_equal(c(7.0), 7.0)\n    assert_raises(ValueError, c, 3.99)\n    assert_raises(ValueError, c, 9.01)\n    # and reordering should not have any effect\n    c = ct.Constraints(ct.EnsureRange(max=4), ct.EnsureRange(min=9), ct.EnsureFloat())\n    assert_raises(ValueError, c, 3.99)\n    assert_raises(ValueError, c, 9.01)\n\n\ndef test_altconstraints():\n    # this should always work\n    c = ct.AltConstraints(ct.EnsureFloat())\n    assert_equal(c(7.0), 7.0)\n    c = ct.AltConstraints(ct.EnsureFloat(), ct.EnsureNone())\n    assert_equal(c.short_description(), '(float or None)')\n    assert_equal(c(7.0), 7.0)\n    assert_equal(c(None), None)\n    # __or__ form\n    c = ct.EnsureFloat() | ct.EnsureNone()\n    assert_equal(c(7.0), 7.0)\n    assert_equal(c(None), None)\n\n    # this should always fail\n    c = ct.Constraints(ct.EnsureRange(min=0, max=4), ct.EnsureRange(min=9, max=11))\n    assert_raises(ValueError, c, 7.0)\n    c = ct.EnsureRange(min=0, max=4) | ct.EnsureRange(min=9, max=11)\n    assert_equal(c(3.0), 3.0)\n    assert_equal(c(9.0), 9.0)\n    assert_raises(ValueError, c, 7.0)\n    assert_raises(ValueError, c, -1.0)\n\n\ndef test_both():\n    # this should always work\n    c = ct.AltConstraints(\n        ct.Constraints(\n            ct.EnsureFloat(),\n            ct.EnsureRange(min=7.0, max=44.0)),\n        ct.EnsureNone())\n    assert_equal(c(7.0), 7.0)\n    assert_equal(c(None), None)\n    # this should always fail\n    assert_raises(ValueError, lambda: c(77.0))\n\ndef test_type_str():\n    assert_equal(ct._type_str((str,)), 'str')\n    assert_equal(ct._type_str(str), 'str')\n",
            "header": {
                "file": {
                    "path": "datalad/tests/test_constraints.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "0e38c7690",
                "new_commit_hash": "0881547fd",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/datalad/tests/test_constraints.py"
            },
            "plus_file": {
                "path": "b/datalad/tests/test_constraints.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 158,
                            "length": 12
                        },
                        "new_range": {
                            "start": 158,
                            "length": 14
                        },
                        "section": "def test_listof():"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    c = ct.EnsureListOf(str)",
                                "type": "context"
                            },
                            {
                                "content": "    assert_equal(c(['a', 'b']), ['a', 'b'])",
                                "type": "context"
                            },
                            {
                                "content": "    assert_equal(c(['a1', 'b2']), ['a1', 'b2'])",
                                "type": "context"
                            },
                            {
                                "content": "    assert_equal(c('a1 b2'), ['a1 b2'])",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def test_tupleof():",
                                "type": "context"
                            },
                            {
                                "content": "    c = ct.EnsureTupleOf(str)",
                                "type": "context"
                            },
                            {
                                "content": "    assert_equal(c(('a', 'b')), ('a', 'b'))",
                                "type": "context"
                            },
                            {
                                "content": "    assert_equal(c(('a1', 'b2')), ('a1', 'b2'))",
                                "type": "context"
                            },
                            {
                                "content": "    assert_equal(c('a1 b2'), ('a1 b2',))",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def test_constraints():",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "datalad/tests/test_constraints.py",
                            "type": "function",
                            "name": "test_listof",
                            "content": "def test_listof():\n    c = ct.EnsureListOf(str)\n    assert_equal(c(['a', 'b']), ['a', 'b'])\n    assert_equal(c(['a1', 'b2']), ['a1', 'b2'])\n    assert_equal(c('a1 b2'), ['a1 b2'])",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 157,
                            "end_lineno": 161,
                            "parent": null
                        },
                        {
                            "file_name": "datalad/tests/test_constraints.py",
                            "type": "function",
                            "name": "test_tupleof",
                            "content": "def test_tupleof():\n    c = ct.EnsureTupleOf(str)\n    assert_equal(c(('a', 'b')), ('a', 'b'))\n    assert_equal(c(('a1', 'b2')), ('a1', 'b2'))\n    assert_equal(c('a1 b2'), ('a1 b2',))",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 164,
                            "end_lineno": 168,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "59f65aecd1b06fcca4a98aa4e256087d678403e9^",
    "new_commit_hash": "59f65aecd1b06fcca4a98aa4e256087d678403e9",
    "commit_message": "Fix EnsureListOf() and EnsureTupleOf() for string inputs",
    "commit_date": "2023-01-19T12:55:39+01:00",
    "metadata": {}
}
{
    "repo_name": "datalad",
    "new_commit_hash": "59f65aecd1b06fcca4a98aa4e256087d678403e9",
    "test_file_codes": [
        "# emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*-\n# vi: set ft=python sts=4 ts=4 sw=4 et:\n### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the DataLad package for the\n#   copyright and license terms.\n#\n### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n'''Unit tests for basic constraints functionality.'''\n\n\nfrom datalad.tests.utils_pytest import (\n    assert_equal,\n    assert_raises,\n)\n\nfrom datalad.support import constraints as ct\n\n\ndef test_int():\n    c = ct.EnsureInt()\n    # this should always work\n    assert_equal(c(7), 7)\n    assert_equal(c(7.0), 7)\n    assert_equal(c('7'), 7)\n    assert_equal(c([7, 3]), [7, 3])\n    # this should always fail\n    assert_raises(ValueError, lambda: c('fail'))\n    assert_raises(ValueError, lambda: c([3, 'fail']))\n    # this will also fail\n    assert_raises(ValueError, lambda: c('17.0'))\n    assert_equal(c.short_description(), 'int')\n\n\ndef test_float():\n    c = ct.EnsureFloat()\n    # this should always work\n    assert_equal(c(7.0), 7.0)\n    assert_equal(c(7), 7.0)\n    assert_equal(c('7'), 7.0)\n    assert_equal(c([7.0, '3.0']), [7.0, 3.0])\n    # this should always fail\n    assert_raises(ValueError, lambda: c('fail'))\n    assert_raises(ValueError, lambda: c([3.0, 'fail']))\n\n\ndef test_bool():\n    c = ct.EnsureBool()\n    # this should always work\n    assert_equal(c(True), True)\n    assert_equal(c(False), False)\n    # all that resuls in True\n    assert_equal(c('True'), True)\n    assert_equal(c('true'), True)\n    assert_equal(c('1'), True)\n    assert_equal(c('yes'), True)\n    assert_equal(c('on'), True)\n    assert_equal(c('enable'), True)\n    # all that resuls in False\n    assert_equal(c('false'), False)\n    assert_equal(c('False'), False)\n    assert_equal(c('0'), False)\n    assert_equal(c('no'), False)\n    assert_equal(c('off'), False)\n    assert_equal(c('disable'), False)\n    # this should always fail\n    assert_raises(ValueError, c, 0)\n    assert_raises(ValueError, c, 1)\n\n\ndef test_str():\n    c = ct.EnsureStr()\n    # this should always work\n    assert_equal(c('hello'), 'hello')\n    assert_equal(c('7.0'), '7.0')\n    # this should always fail\n    assert_raises(ValueError, lambda: c(['ab']))\n    assert_raises(ValueError, lambda: c(['a', 'b']))\n    assert_raises(ValueError, lambda: c(('a', 'b')))\n    # no automatic conversion attempted\n    assert_raises(ValueError, lambda: c(7.0))\n    assert_equal(c.short_description(), 'str')\n\ndef test_str_min_len():\n    c = ct.EnsureStr(min_len=1)\n    assert_equal(c('hello'), 'hello')\n    assert_equal(c('h'), 'h')\n    assert_raises(ValueError, c, '')\n\n    c = ct.EnsureStr(min_len=2)\n    assert_equal(c('hello'), 'hello')\n    assert_raises(ValueError, c, 'h')\n\n\ndef test_none():\n    c = ct.EnsureNone()\n    # this should always work\n    assert_equal(c(None), None)\n    # instance of NoneDeprecated is also None\n    assert_equal(c(ct.NoneDeprecated), None)\n    # this should always fail\n    assert_raises(ValueError, lambda: c('None'))\n    assert_raises(ValueError, lambda: c([]))\n\n\ndef test_callable():\n    c = ct.EnsureCallable()\n    # this should always work\n    assert_equal(c(range), range)\n    assert_raises(ValueError, c, 'range')\n\n\ndef test_choice():\n    c = ct.EnsureChoice('choice1', 'choice2', None)\n    # this should always work\n    assert_equal(c('choice1'), 'choice1')\n    assert_equal(c(None), None)\n    # this should always fail\n    assert_raises(ValueError, lambda: c('fail'))\n    assert_raises(ValueError, lambda: c('None'))\n\n\ndef test_keychoice():\n    c = ct.EnsureKeyChoice(key='some', values=('choice1', 'choice2', None))\n    assert_equal(c({'some': 'choice1'}), {'some': 'choice1'})\n    assert_equal(c({'some': None}), {'some': None})\n    assert_equal(c({'some': None, 'ign': 'ore'}), {'some': None, 'ign': 'ore'})\n    assert_raises(ValueError, c, 'fail')\n    assert_raises(ValueError, c, 'None')\n    assert_raises(ValueError, c, {'nope': 'None'})\n    assert_raises(ValueError, c, {'some': 'None'})\n    assert_raises(ValueError, c, {'some': ('a', 'b')})\n\n\ndef test_range():\n    c = ct.EnsureRange(min=3, max=7)\n    # this should always work\n    assert_equal(c(3.0), 3.0)\n\n    # this should always fail\n    assert_raises(ValueError, lambda: c(2.9999999))\n    assert_raises(ValueError, lambda: c(77))\n    assert_raises(TypeError, lambda: c('fail'))\n    assert_raises(TypeError, lambda: c((3, 4)))\n    # since no type checks are performed\n    assert_raises(TypeError, lambda: c('7'))\n\n    # Range doesn't have to be numeric\n    c = ct.EnsureRange(min=\"e\", max=\"qqq\")\n    assert_equal(c('e'), 'e')\n    assert_equal(c('fa'), 'fa')\n    assert_equal(c('qq'), 'qq')\n    assert_raises(ValueError, c, 'a')\n    assert_raises(ValueError, c, 'qqqa')\n\n\ndef test_listof():\n    c = ct.EnsureListOf(str)\n    assert_equal(c(['a', 'b']), ['a', 'b'])\n    assert_equal(c(['a1', 'b2']), ['a1', 'b2'])\n    assert_equal(c('a1 b2'), ['a1 b2'])\n\n\ndef test_tupleof():\n    c = ct.EnsureTupleOf(str)\n    assert_equal(c(('a', 'b')), ('a', 'b'))\n    assert_equal(c(('a1', 'b2')), ('a1', 'b2'))\n    assert_equal(c('a1 b2'), ('a1 b2',))\n\n\ndef test_constraints():\n    # this should always work\n    c = ct.Constraints(ct.EnsureFloat())\n    assert_equal(c(7.0), 7.0)\n    c = ct.Constraints(ct.EnsureFloat(), ct.EnsureRange(min=4.0))\n    assert_equal(c(7.0), 7.0)\n    # __and__ form\n    c = ct.EnsureFloat() & ct.EnsureRange(min=4.0)\n    assert_equal(c(7.0), 7.0)\n    assert_raises(ValueError, c, 3.9)\n    c = ct.Constraints(ct.EnsureFloat(), ct.EnsureRange(min=4), ct.EnsureRange(max=9))\n    assert_equal(c(7.0), 7.0)\n    assert_raises(ValueError, c, 3.9)\n    assert_raises(ValueError, c, 9.01)\n    # __and__ form\n    c = ct.EnsureFloat() & ct.EnsureRange(min=4) & ct.EnsureRange(max=9)\n    assert_equal(c(7.0), 7.0)\n    assert_raises(ValueError, c, 3.99)\n    assert_raises(ValueError, c, 9.01)\n    # and reordering should not have any effect\n    c = ct.Constraints(ct.EnsureRange(max=4), ct.EnsureRange(min=9), ct.EnsureFloat())\n    assert_raises(ValueError, c, 3.99)\n    assert_raises(ValueError, c, 9.01)\n\n\ndef test_altconstraints():\n    # this should always work\n    c = ct.AltConstraints(ct.EnsureFloat())\n    assert_equal(c(7.0), 7.0)\n    c = ct.AltConstraints(ct.EnsureFloat(), ct.EnsureNone())\n    assert_equal(c.short_description(), '(float or None)')\n    assert_equal(c(7.0), 7.0)\n    assert_equal(c(None), None)\n    # __or__ form\n    c = ct.EnsureFloat() | ct.EnsureNone()\n    assert_equal(c(7.0), 7.0)\n    assert_equal(c(None), None)\n\n    # this should always fail\n    c = ct.Constraints(ct.EnsureRange(min=0, max=4), ct.EnsureRange(min=9, max=11))\n    assert_raises(ValueError, c, 7.0)\n    c = ct.EnsureRange(min=0, max=4) | ct.EnsureRange(min=9, max=11)\n    assert_equal(c(3.0), 3.0)\n    assert_equal(c(9.0), 9.0)\n    assert_raises(ValueError, c, 7.0)\n    assert_raises(ValueError, c, -1.0)\n\n\ndef test_both():\n    # this should always work\n    c = ct.AltConstraints(\n        ct.Constraints(\n            ct.EnsureFloat(),\n            ct.EnsureRange(min=7.0, max=44.0)),\n        ct.EnsureNone())\n    assert_equal(c(7.0), 7.0)\n    assert_equal(c(None), None)\n    # this should always fail\n    assert_raises(ValueError, lambda: c(77.0))\n\ndef test_type_str():\n    assert_equal(ct._type_str((str,)), 'str')\n    assert_equal(ct._type_str(str), 'str')\n",
        "import pytest\nimport tempfile\nimport os\n\n\n@pytest.fixture\ndef path():\n    with tempfile.TemporaryDirectory() as tmpdir:\n        yield tmpdir\n\n\n@pytest.fixture(autouse=True)\ndef setup_git_config():\n    with tempfile.TemporaryDirectory() as home:\n        old_home = os.environ.get(\"HOME\")\n        os.environ[\"HOME\"] = home\n        os.system('git config --global user.name \"DataLad Tester\"')\n        os.system('git config --global user.email \"test@example.com\"')\n        yield\n        if old_home:\n            os.environ[\"HOME\"] = old_home\n"
    ],
    "test_file_names": [
        "test_1.py",
        "conftest.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Datalad installation attempts...\nVerifying installation...\nDatalad version: 0.18.1+7.g59f65aecd\n✅ Installation successful!\nSuccessfully installed Datalad using Python 3.9\n",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 25 packages in 94ms\nInstalled 25 packages in 4.57s\n + coverage==7.6.10\n + docopt==0.6.2\n + exceptiongroup==1.2.2\n + grabbit==0.2.6\n + importlib-resources==6.4.5\n + iniconfig==2.0.0\n + nibabel==5.3.2\n + num2words==0.5.14\n + numpy==2.0.2\n + packaging==24.2\n + pandas==2.2.3\n + patsy==1.0.1\n + pluggy==1.5.0\n + pybids==0.6.5\n + pytest==8.3.4\n + pytest-cov==6.0.0\n + python-dateutil==2.9.0.post0\n + pytz==2024.2\n + scipy==1.13.1\n + setuptools==75.6.0\n + six==1.17.0\n + tomli==2.2.1\n + typing-extensions==4.12.2\n + tzdata==2024.2\n + zipp==3.21.0\nResolved 65 packages in 5.07s\nPrepared 1 package in 3.17s\nInstalled 53 packages in 362ms\n + annexremote==1.6.6\n + argcomplete==3.5.3\n + backports-tarfile==1.2.0\n + beautifulsoup4==4.12.3\n + boto==2.49.0\n + certifi==2024.12.14\n + cffi==1.17.1\n + chardet==4.0.0\n + charset-normalizer==3.4.1\n + citeproc-py==0.6.0\n + cryptography==44.0.0\n + datalad==0.18.1+7.g59f65aecd (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_59f65aecd1b06fcca4a98aa4e256087d678403e9)\n + distro==1.9.0\n + duecredit==0.10.2\n + fasteners==0.19\n + httpretty==1.1.4\n + humanize==4.11.0\n + idna==3.10\n + importlib-metadata==8.5.0\n + iso8601==2.1.0\n + jaraco-classes==3.4.0\n + jaraco-context==6.0.1\n + jaraco-functools==4.1.0\n + jeepney==0.8.0\n + keyring==25.6.0\n + keyrings-alt==5.0.2\n + looseversion==1.3.0\n + lxml==5.3.0\n + more-itertools==10.5.0\n + msgpack==1.1.0\n + multidict==6.1.0\n + mypy==0.991\n + mypy-extensions==1.0.0\n + patool==1.15.0\n + platformdirs==4.3.6\n + propcache==0.2.1\n + pycparser==2.22\n + pyperclip==1.9.0\n + pytest-fail-slow==0.6.0\n + python-gitlab==5.3.0\n + pyyaml==6.0.2\n + requests==2.32.3\n + requests-ftp==0.3.1\n + requests-toolbelt==1.0.0\n + secretstorage==3.3.3\n + soupsieve==2.6\n + tqdm==4.67.1\n + types-python-dateutil==2.9.0.20241206\n + types-requests==2.32.0.20241016\n + urllib3==2.3.0\n + vcrpy==4.3.0\n + wrapt==1.17.0\n + yarl==1.18.3\nResolved 97 packages in 168ms\nInstalled 32 packages in 565ms\n + alabaster==0.7.16\n + asv==0.6.4\n + asv-runner==0.2.1\n + babel==2.16.0\n + build==1.2.2.post1\n + distlib==0.3.9\n + docutils==0.21.2\n + execnet==2.1.1\n + filelock==3.16.1\n + gprof2dot==2024.6.6\n + imagesize==1.4.1\n + jinja2==3.1.5\n + json5==0.10.0\n + markupsafe==3.0.2\n + psutil==6.1.1\n + pygments==2.18.0\n + pympler==1.1\n + pypandoc==1.14\n + pyproject-hooks==1.2.0\n + pytest-xdist==3.6.1\n + snowballstemmer==2.2.0\n + sphinx==7.4.7\n + sphinx-rtd-theme==3.0.2\n + sphinxcontrib-applehelp==2.0.0\n + sphinxcontrib-devhelp==2.0.0\n + sphinxcontrib-htmlhelp==2.1.0\n + sphinxcontrib-jquery==4.1\n + sphinxcontrib-jsmath==1.0.1\n + sphinxcontrib-qthelp==2.0.0\n + sphinxcontrib-serializinghtml==2.0.0\n + tabulate==0.9.0\n + virtualenv==20.28.0\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_59f65aecd1b06fcca4a98aa4e256087d678403e9\nconfigfile: tox.ini\nplugins: fail-slow-0.6.0, xdist-3.6.1, cov-6.0.0\ncollected 16 items\n\nr2e_tests/test_1.py ................                                     [100%]\n\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_int\nPASSED r2e_tests/test_1.py::test_float\nPASSED r2e_tests/test_1.py::test_bool\nPASSED r2e_tests/test_1.py::test_str\nPASSED r2e_tests/test_1.py::test_str_min_len\nPASSED r2e_tests/test_1.py::test_none\nPASSED r2e_tests/test_1.py::test_callable\nPASSED r2e_tests/test_1.py::test_choice\nPASSED r2e_tests/test_1.py::test_keychoice\nPASSED r2e_tests/test_1.py::test_range\nPASSED r2e_tests/test_1.py::test_listof\nPASSED r2e_tests/test_1.py::test_tupleof\nPASSED r2e_tests/test_1.py::test_constraints\nPASSED r2e_tests/test_1.py::test_altconstraints\nPASSED r2e_tests/test_1.py::test_both\nPASSED r2e_tests/test_1.py::test_type_str\n============================== 16 passed in 1.80s ==============================\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "============================= test session starts ==============================\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/datalad_59f65aecd1b06fcca4a98aa4e256087d678403e9\nconfigfile: tox.ini\nplugins: fail-slow-0.6.0, xdist-3.6.1, cov-6.0.0\ncollected 16 items\n\nr2e_tests/test_1.py ..........FF....                                     [100%]\n\n=================================== FAILURES ===================================\n_________________________________ test_listof __________________________________\n\n    def test_listof():\n        c = ct.EnsureListOf(str)\n        assert_equal(c(['a', 'b']), ['a', 'b'])\n        assert_equal(c(['a1', 'b2']), ['a1', 'b2'])\n>       assert_equal(c('a1 b2'), ['a1 b2'])\n\nr2e_tests/test_1.py:161: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nfirst = ['a', '1', ' ', 'b', '2'], second = ['a1 b2'], msg = None\n\n    def assert_equal(first, second, msg=None):\n        if msg is None:\n>           assert first == second\nE           AssertionError: assert ['a', '1', ' ', 'b', '2'] == ['a1 b2']\nE             \nE             At index 0 diff: 'a' != 'a1 b2'\nE             Left contains 4 more items, first extra item: '1'\nE             Use -v to get more diff\n\ndatalad/tests/utils_pytest.py:73: AssertionError\n_________________________________ test_tupleof _________________________________\n\n    def test_tupleof():\n        c = ct.EnsureTupleOf(str)\n        assert_equal(c(('a', 'b')), ('a', 'b'))\n        assert_equal(c(('a1', 'b2')), ('a1', 'b2'))\n>       assert_equal(c('a1 b2'), ('a1 b2',))\n\nr2e_tests/test_1.py:168: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nfirst = ('a', '1', ' ', 'b', '2'), second = ('a1 b2',), msg = None\n\n    def assert_equal(first, second, msg=None):\n        if msg is None:\n>           assert first == second\nE           AssertionError: assert ('a', '1', ' ', 'b', '2') == ('a1 b2',)\nE             \nE             At index 0 diff: 'a' != 'a1 b2'\nE             Left contains 4 more items, first extra item: '1'\nE             Use -v to get more diff\n\ndatalad/tests/utils_pytest.py:73: AssertionError\n==================================== PASSES ====================================\n=========================== short test summary info ============================\nPASSED r2e_tests/test_1.py::test_int\nPASSED r2e_tests/test_1.py::test_float\nPASSED r2e_tests/test_1.py::test_bool\nPASSED r2e_tests/test_1.py::test_str\nPASSED r2e_tests/test_1.py::test_str_min_len\nPASSED r2e_tests/test_1.py::test_none\nPASSED r2e_tests/test_1.py::test_callable\nPASSED r2e_tests/test_1.py::test_choice\nPASSED r2e_tests/test_1.py::test_keychoice\nPASSED r2e_tests/test_1.py::test_range\nPASSED r2e_tests/test_1.py::test_constraints\nPASSED r2e_tests/test_1.py::test_altconstraints\nPASSED r2e_tests/test_1.py::test_both\nPASSED r2e_tests/test_1.py::test_type_str\nFAILED r2e_tests/test_1.py::test_listof - AssertionError: assert ['a', '1', '...\nFAILED r2e_tests/test_1.py::test_tupleof - AssertionError: assert ('a', '1', ...\n========================= 2 failed, 14 passed in 0.49s =========================\n",
    "old_commit_res_stderr": ""
}
datalad/support/constraints.py datalad/tests/test_constraints.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** EnsureListOf and EnsureTupleOf Incorrectly Split String Inputs into Characters

**Description:**
When using `EnsureListOf` or `EnsureTupleOf` with a string input, the functions mistakenly break the string into individual characters instead of treating the entire string as a single element.

**Example Code:**
```python
c = EnsureListOf(str)
result = c('a1 b2')
print(result)  # Expected: ['a1 b2'], Got: ['a', '1', ' ', 'b', '2']

c = EnsureTupleOf(str)
result = c('a1 b2')
print(result)  # Expected: ('a1 b2',), Got: ('a', '1', ' ', 'b', '2')
```

**Expected Behavior:**
- `EnsureListOf(str)` should return `['a1 b2']` when given `'a1 b2'` as input.
- `EnsureTupleOf(str)` should return `('a1 b2',)` when given `'a1 b2'` as input.

**Actual Behavior:**
- `EnsureListOf(str)` returns `['a', '1', ' ', 'b', '2']`.
- `EnsureTupleOf(str)` returns `('a', '1', ' ', 'b', '2')`.

This behavior is unintended and causes the functions to misinterpret string inputs by splitting them into individual characters rather than treating the entire string as a single list or tuple element.
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### datalad/support/constraints.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 et:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Helper for parameter validation, documentation and conversion"""

__docformat__ = 'restructuredtext'

import re


def _strip_typerepr(s):
    """Strip away <class '...'> and <type '...'> decorations for docstrings
    """
    return re.sub(r"<(class|type) '(\S+)'>", r'\2', s)


def _type_str(t):
    """Get string human-readable representation of a data type

    If type (t) is given as a tuple, assume ability to choose any of the
    listed types, so those types listing get joined with |
    """
    if isinstance(t, tuple):
        s = ' or '.join(map(_type_str, t))
        return ("(%s)" % s) if len(t) > 1 else s
    return _strip_typerepr(str(t))


class _NoneDeprecated:
    """A helper construct to ease migrations for option=None.

    Should be instantiated, so it gains `__repr__` and thus would render nicely
    in docs etc.
    """
    def __repr__(self):
        return 'None(DEPRECATED)'


NoneDeprecated = _NoneDeprecated()


class Constraint(object):
    """Base class for input value conversion/validation.

    These classes are also meant to be able to generate appropriate
    documentation on an appropriate parameter value.
    """

    # TODO: __str__ and/or __repr__ for every one of them

    def __repr__(self):
        """Rudimentary repr to avoid default scary to the user Python repr"""
        return "constraint:%s" % self.short_description()

    def __and__(self, other):
        return Constraints(self, other)

    def __or__(self, other):
        return AltConstraints(self, other)

    def __call__(self, value):
        # do any necessary checks or conversions, potentially catch exceptions
        # and generate a meaningful error message
        raise NotImplementedError("abstract class")

    def long_description(self):
        # return meaningful docs or None
        # used as a comprehensive description in the parameter list
        return self.short_description()

    def short_description(self):
        # return meaningful docs or None
        # used as a condensed primer for the parameter lists
        raise NotImplementedError("abstract class")


class EnsureDType(Constraint):
    """Ensure that an input (or several inputs) are of a particular data type.
    """
    # TODO extend to support numpy-like dtype specs, e.g. 'int64'
    # in addition to functors
    def __init__(self, dtype):
        """
        Parameters
        ----------
        dtype : functor
        """
        self._dtype = dtype

    def __call__(self, value):
        if hasattr(value, '__iter__') and \
                not (isinstance(value, (bytes, str))):
            return list(map(self._dtype, value))
        else:
            return self._dtype(value)

    def short_description(self):
        return _type_str(self._dtype)

    def long_description(self):
        return "value must be convertible to type '%s'" % self.short_description()


class EnsureInt(EnsureDType):
    """Ensure that an input (or several inputs) are of a data type 'int'.
    """
    def __init__(self):
        """Initializes EnsureDType with int"""
        EnsureDType.__init__(self, int)


class EnsureFloat(EnsureDType):
    """Ensure that an input (or several inputs) are of a data type 'float'.
    """
    def __init__(self):
        """Initializes EnsureDType with float"""
        EnsureDType.__init__(self, float)


class EnsureListOf(Constraint):
    """Ensure that an input is a list of a particular data type
    """
    def __init__(self, dtype):
        """
        Parameters
        ----------
        dtype : functor
        """
        self._dtype = dtype
        super(EnsureListOf, self).__init__()

    def __call__(self, value):
        return list(map(self._dtype, value))

    def short_description(self):
        return 'list(%s)' % _type_str(self._dtype)

    def long_description(self):
        return "value must be convertible to %s" % self.short_description()


class EnsureTupleOf(Constraint):
    """Ensure that an input is a tuple of a particular data type
    """
    def __init__(self, dtype):
        """
        Parameters
        ----------
        dtype : functor
        """
        self._dtype = dtype
        super(EnsureTupleOf, self).__init__()

    def __call__(self, value):
        return tuple(map(self._dtype, value))

    def short_description(self):
        return 'tuple(%s)' % _type_str(self._dtype)

    def long_description(self):
        return "value must be convertible to %s" % self.short_description()


class EnsureBool(Constraint):
    """Ensure that an input is a bool.

    A couple of literal labels are supported, such as:
    False: '0', 'no', 'off', 'disable', 'false'
    True: '1', 'yes', 'on', 'enable', 'true'
    """
    def __call__(self, value):
        if isinstance(value, bool):
            return value
        elif isinstance(value, (bytes, str)):
            value = value.lower()
            if value in ('0', 'no', 'off', 'disable', 'false'):
                return False
            elif value in ('1', 'yes', 'on', 'enable', 'true'):
                return True
        raise ValueError(
            "value '{}' must be convertible to boolean".format(
                value))

    def long_description(self):
        return 'value must be convertible to type bool'

    def short_description(self):
        return 'bool'


class EnsureStr(Constraint):
    """Ensure an input is a string.

    No automatic conversion is attempted.
    """
    def __init__(self, min_len=0):
        """
        Parameters
        ----------
        min_len: int, optional
           Minimal length for a string.
        """
        assert(min_len >= 0)
        self._min_len = min_len
        super(EnsureStr, self).__init__()

    def __call__(self, value):
        if not isinstance(value, (bytes, str)):
            # do not perform a blind conversion ala str(), as almost
            # anything can be converted and the result is most likely
            # unintended
            raise ValueError("%s is not a string" % repr(value))
        if len(value) < self._min_len:
            raise ValueError("%r is shorter than of minimal length %d"
                             % (value, self._min_len))
        return value

    def long_description(self):
        return 'value must be a string'

    def short_description(self):
        return 'str'


class EnsureStrPrefix(EnsureStr):
    """Ensure an input is a string that starts with a given prefix.
    """
    def __init__(self, prefix):
        """
        Parameters
        ----------
        prefix : str
           Mandatory prefix.
        """
        self._prefix = prefix
        super().__init__()

    def __call__(self, value):
        super().__call__(value)
        if not value.startswith(self._prefix):
            raise ValueError("%r does not start with '%s'"
                             % (value, self._prefix))
        return value

    def long_description(self):
        return "value must start with '{}'".format(self._prefix)

    def short_description(self):
        return '{}...'.format(self._prefix)


class EnsureNone(Constraint):
    """Ensure an input is of value `None`"""
    def __call__(self, value):
        if value is None or isinstance(value, _NoneDeprecated):
            return None
        else:
            raise ValueError("value must be `None`")

    def short_description(self):
        return 'None'

    def long_description(self):
        return 'value must be `None`'


class EnsureCallable(Constraint):
    """Ensure an input is a callable object"""
    def __call__(self, value):
        if hasattr(value, '__call__'):
            return value
        else:
            raise ValueError("value must be a callable")

    def short_description(self):
        return 'callable'

    def long_description(self):
        return 'value must be a callable'


class EnsureChoice(Constraint):
    """Ensure an input is element of a set of possible values"""

    def __init__(self, *values):
        """
        Parameters
        ----------
        *values
           Possible accepted values.
        """
        self._allowed = values
        super(EnsureChoice, self).__init__()

    def __call__(self, value):
        if value not in self._allowed:
            raise ValueError(f"value {value} is not one of {self._allowed}")
        return value

    def long_description(self):
        return 'value must be one of [CMD: %s CMD][PY: %s PY]' % (
            str(tuple(i for i in self._allowed if i is not None)),
            str(self._allowed)
        )

    def short_description(self):
        return '{%s}' % ', '.join([repr(c) for c in self._allowed])


class EnsureKeyChoice(EnsureChoice):
    """Ensure value under a key in an input is in a set of possible values"""

    def __init__(self, key, values):
        """
        Parameters
        ----------
        key : str
          The to-be-tested values are looked up under the given key in
          a dict-like input object.
        values : tuple
           Possible accepted values.
        """
        self._key = key
        super(EnsureKeyChoice, self).__init__(*values)

    def __call__(self, value):
        if self._key not in value:
            raise ValueError("value not dict-like")
        super(EnsureKeyChoice, self).__call__(value[self._key])
        return value

    def long_description(self):
        return "value in '%s' must be one of %s" % (self._key, str(self._allowed),)

    def short_description(self):
        return '%s:{%s}' % (self._key, ', '.join([str(c) for c in self._allowed]))


class EnsureRange(Constraint):
    """Ensure an input is within a particular range

    No type checks are performed.
    """
    def __init__(self, min=None, max=None):
        """
        Parameters
        ----------
        min
            Minimal value to be accepted in the range
        max
            Maximal value to be accepted in the range
        """
        self._min = min
        self._max = max
        super(EnsureRange, self).__init__()

    def __call__(self, value):
        if self._min is not None:
            if value < self._min:
                raise ValueError("value must be at least %s" % (self._min,))
        if self._max is not None:
            if value > self._max:
                raise ValueError("value must be at most %s" % (self._max,))
        return value

    def long_description(self):
        min_str = '-inf' if self._min is None else str(self._min)
        max_str = 'inf' if self._max is None else str(self._max)
        return 'value must be in range [%s, %s]' % (min_str, max_str)

    def short_description(self):
        return None


class _MultiConstraint(Constraint):
    """Helper class to override the description methods to reported
    multiple constraints
    """
    def _get_description(self, attr):
        cs = [
            getattr(c, attr)()
            for c in self.constraints
            if hasattr(c, attr)
        ]
        cs = [c for c in cs if c is not None]
        doc = ' or '.join(cs)
        if len(cs) > 1:
            return f'({doc})'
        else:
            return doc

    def long_description(self):
        return self._get_description('long_description')

    def short_description(self):
        return self._get_description('short_description')


class AltConstraints(_MultiConstraint):
    """Logical OR for constraints.

    An arbitrary number of constraints can be given. They are evaluated in the
    order in which they were specified. The value returned by the first
    constraint that does not raise an exception is the global return value.

    Documentation is aggregated for all alternative constraints.
    """
    def __init__(self, *constraints):
        """
        Parameters
        ----------
        *constraints
           Alternative constraints
        """
        super(AltConstraints, self).__init__()
        self.constraints = [EnsureNone() if c is None else c for c in constraints]

    def __or__(self, other):
        if isinstance(other, AltConstraints):
            self.constraints.extend(other.constraints)
        else:
            self.constraints.append(other)
        return self

    def __call__(self, value):
        e_list = []
        for c in self.constraints:
            try:
                return c(value)
            except Exception as e:
                e_list.append(e)
        raise ValueError(f"{value} does not match any alternative: "
                         f"{self.constraints} {e_list}")


class Constraints(_MultiConstraint):
    """Logical AND for constraints.

    An arbitrary number of constraints can be given. They are evaluated in the
    order in which they were specified. The return value of each constraint is
    passed an input into the next. The return value of the last constraint
    is the global return value. No intermediate exceptions are caught.

    Documentation is aggregated for all constraints.
    """
    def __init__(self, *constraints):
        """
        Parameters
        ----------
        *constraints
           Constraints all of which must be satisfied
        """
        super(Constraints, self).__init__()
        self.constraints = [EnsureNone() if c is None else c for c in constraints]

    def __and__(self, other):
        if isinstance(other, Constraints):
            self.constraints.extend(other.constraints)
        else:
            self.constraints.append(other)
        return self

    def __call__(self, value):
        for c in (self.constraints):
            value = c(value)
        return value


constraint_spec_map = {
    'float': EnsureFloat(),
    'int': EnsureInt(),
    'bool': EnsureBool(),
    'str': EnsureStr(),
}


def expand_constraint_spec(spec):
    """Helper to translate literal constraint specs into functional ones

    e.g. 'float' -> EnsureFloat()
    """
    if spec is None or hasattr(spec, '__call__'):
        return spec
    else:
        try:
            return constraint_spec_map[spec]
        except KeyError:
            raise ValueError("unsupported constraint specification '%r'" % (spec,))

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]
**Title:** EnsureListOf and EnsureTupleOf Incorrectly Split String Inputs into Characters

**Description:**
When using `EnsureListOf` or `EnsureTupleOf` with a string input, the functions mistakenly break the string into individual characters instead of treating the entire string as a single element.

**Example Code:**
```python
c = EnsureListOf(str)
result = c('a1 b2')
print(result)  # Expected: ['a1 b2'], Got: ['a', '1', ' ', 'b', '2']

c = EnsureTupleOf(str)
result = c('a1 b2')
print(result)  # Expected: ('a1 b2',), Got: ('a', '1', ' ', 'b', '2')
```

**Expected Behavior:**
- `EnsureListOf(str)` should return `['a1 b2']` when given `'a1 b2'` as input.
- `EnsureTupleOf(str)` should return `('a1 b2',)` when given `'a1 b2'` as input.

**Actual Behavior:**
- `EnsureListOf(str)` returns `['a', '1', ' ', 'b', '2']`.
- `EnsureTupleOf(str)` returns `('a', '1', ' ', 'b', '2')`.

This behavior is unintended and causes the functions to misinterpret string inputs by splitting them into individual characters rather than treating the entire string as a single list or tuple element.
[/ISSUE]
{
    "test_int": "PASSED",
    "test_float": "PASSED",
    "test_bool": "PASSED",
    "test_str": "PASSED",
    "test_str_min_len": "PASSED",
    "test_none": "PASSED",
    "test_callable": "PASSED",
    "test_choice": "PASSED",
    "test_keychoice": "PASSED",
    "test_range": "PASSED",
    "test_listof": "PASSED",
    "test_tupleof": "PASSED",
    "test_constraints": "PASSED",
    "test_altconstraints": "PASSED",
    "test_both": "PASSED",
    "test_type_str": "PASSED"
}
diff --git a/datalad/support/constraints.py b/datalad/support/constraints.py
index 0f7533485..623fab363 100644
--- a/datalad/support/constraints.py
+++ b/datalad/support/constraints.py
@@ -135,7 +135,7 @@ class EnsureListOf(Constraint):
         super(EnsureListOf, self).__init__()
 
     def __call__(self, value):
-        return list(map(self._dtype, value))
+        return list(map(self._dtype, ([value] if isinstance(value, str) else value)))
 
     def short_description(self):
         return 'list(%s)' % _type_str(self._dtype)
@@ -157,7 +157,7 @@ class EnsureTupleOf(Constraint):
         super(EnsureTupleOf, self).__init__()
 
     def __call__(self, value):
-        return tuple(map(self._dtype, value))
+        return tuple(map(self._dtype, ([value] if isinstance(value, str) else value)))
 
     def short_description(self):
         return 'tuple(%s)' % _type_str(self._dtype)

{"datalad/support/constraints.py": "# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-\n# ex: set sts=4 ts=4 sw=4 et:\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n#\n#   See COPYING file distributed along with the datalad package for the\n#   copyright and license terms.\n#\n# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##\n\"\"\"Helper for parameter validation, documentation and conversion\"\"\"\n\n__docformat__ = 'restructuredtext'\n\nimport re\n\n\ndef _strip_typerepr(s):\n    \"\"\"Strip away <class '...'> and <type '...'> decorations for docstrings\n    \"\"\"\n    return re.sub(r\"<(class|type) '(\\S+)'>\", r'\\2', s)\n\n\ndef _type_str(t):\n    \"\"\"Get string human-readable representation of a data type\n\n    If type (t) is given as a tuple, assume ability to choose any of the\n    listed types, so those types listing get joined with |\n    \"\"\"\n    if isinstance(t, tuple):\n        s = ' or '.join(map(_type_str, t))\n        return (\"(%s)\" % s) if len(t) > 1 else s\n    return _strip_typerepr(str(t))\n\n\nclass _NoneDeprecated:\n    \"\"\"A helper construct to ease migrations for option=None.\n\n    Should be instantiated, so it gains `__repr__` and thus would render nicely\n    in docs etc.\n    \"\"\"\n    def __repr__(self):\n        return 'None(DEPRECATED)'\n\n\nNoneDeprecated = _NoneDeprecated()\n\n\nclass Constraint(object):\n    \"\"\"Base class for input value conversion/validation.\n\n    These classes are also meant to be able to generate appropriate\n    documentation on an appropriate parameter value.\n    \"\"\"\n\n    # TODO: __str__ and/or __repr__ for every one of them\n\n    def __repr__(self):\n        \"\"\"Rudimentary repr to avoid default scary to the user Python repr\"\"\"\n        return \"constraint:%s\" % self.short_description()\n\n    def __and__(self, other):\n        return Constraints(self, other)\n\n    def __or__(self, other):\n        return AltConstraints(self, other)\n\n    def __call__(self, value):\n        # do any necessary checks or conversions, potentially catch exceptions\n        # and generate a meaningful error message\n        raise NotImplementedError(\"abstract class\")\n\n    def long_description(self):\n        # return meaningful docs or None\n        # used as a comprehensive description in the parameter list\n        return self.short_description()\n\n    def short_description(self):\n        # return meaningful docs or None\n        # used as a condensed primer for the parameter lists\n        raise NotImplementedError(\"abstract class\")\n\n\nclass EnsureDType(Constraint):\n    \"\"\"Ensure that an input (or several inputs) are of a particular data type.\n    \"\"\"\n    # TODO extend to support numpy-like dtype specs, e.g. 'int64'\n    # in addition to functors\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n\n    def __call__(self, value):\n        if hasattr(value, '__iter__') and \\\n                not (isinstance(value, (bytes, str))):\n            return list(map(self._dtype, value))\n        else:\n            return self._dtype(value)\n\n    def short_description(self):\n        return _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to type '%s'\" % self.short_description()\n\n\nclass EnsureInt(EnsureDType):\n    \"\"\"Ensure that an input (or several inputs) are of a data type 'int'.\n    \"\"\"\n    def __init__(self):\n        \"\"\"Initializes EnsureDType with int\"\"\"\n        EnsureDType.__init__(self, int)\n\n\nclass EnsureFloat(EnsureDType):\n    \"\"\"Ensure that an input (or several inputs) are of a data type 'float'.\n    \"\"\"\n    def __init__(self):\n        \"\"\"Initializes EnsureDType with float\"\"\"\n        EnsureDType.__init__(self, float)\n\n\nclass EnsureListOf(Constraint):\n    \"\"\"Ensure that an input is a list of a particular data type\n    \"\"\"\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n        super(EnsureListOf, self).__init__()\n\n    def __call__(self, value):\n        return list(map(self._dtype, value))\n\n    def short_description(self):\n        return 'list(%s)' % _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to %s\" % self.short_description()\n\n\nclass EnsureTupleOf(Constraint):\n    \"\"\"Ensure that an input is a tuple of a particular data type\n    \"\"\"\n    def __init__(self, dtype):\n        \"\"\"\n        Parameters\n        ----------\n        dtype : functor\n        \"\"\"\n        self._dtype = dtype\n        super(EnsureTupleOf, self).__init__()\n\n    def __call__(self, value):\n        return tuple(map(self._dtype, value))\n\n    def short_description(self):\n        return 'tuple(%s)' % _type_str(self._dtype)\n\n    def long_description(self):\n        return \"value must be convertible to %s\" % self.short_description()\n\n\nclass EnsureBool(Constraint):\n    \"\"\"Ensure that an input is a bool.\n\n    A couple of literal labels are supported, such as:\n    False: '0', 'no', 'off', 'disable', 'false'\n    True: '1', 'yes', 'on', 'enable', 'true'\n    \"\"\"\n    def __call__(self, value):\n        if isinstance(value, bool):\n            return value\n        elif isinstance(value, (bytes, str)):\n            value = value.lower()\n            if value in ('0', 'no', 'off', 'disable', 'false'):\n                return False\n            elif value in ('1', 'yes', 'on', 'enable', 'true'):\n                return True\n        raise ValueError(\n            \"value '{}' must be convertible to boolean\".format(\n                value))\n\n    def long_description(self):\n        return 'value must be convertible to type bool'\n\n    def short_description(self):\n        return 'bool'\n\n\nclass EnsureStr(Constraint):\n    \"\"\"Ensure an input is a string.\n\n    No automatic conversion is attempted.\n    \"\"\"\n    def __init__(self, min_len=0):\n        \"\"\"\n        Parameters\n        ----------\n        min_len: int, optional\n           Minimal length for a string.\n        \"\"\"\n        assert(min_len >= 0)\n        self._min_len = min_len\n        super(EnsureStr, self).__init__()\n\n    def __call__(self, value):\n        if not isinstance(value, (bytes, str)):\n            # do not perform a blind conversion ala str(), as almost\n            # anything can be converted and the result is most likely\n            # unintended\n            raise ValueError(\"%s is not a string\" % repr(value))\n        if len(value) < self._min_len:\n            raise ValueError(\"%r is shorter than of minimal length %d\"\n                             % (value, self._min_len))\n        return value\n\n    def long_description(self):\n        return 'value must be a string'\n\n    def short_description(self):\n        return 'str'\n\n\nclass EnsureStrPrefix(EnsureStr):\n    \"\"\"Ensure an input is a string that starts with a given prefix.\n    \"\"\"\n    def __init__(self, prefix):\n        \"\"\"\n        Parameters\n        ----------\n        prefix : str\n           Mandatory prefix.\n        \"\"\"\n        self._prefix = prefix\n        super().__init__()\n\n    def __call__(self, value):\n        super().__call__(value)\n        if not value.startswith(self._prefix):\n            raise ValueError(\"%r does not start with '%s'\"\n                             % (value, self._prefix))\n        return value\n\n    def long_description(self):\n        return \"value must start with '{}'\".format(self._prefix)\n\n    def short_description(self):\n        return '{}...'.format(self._prefix)\n\n\nclass EnsureNone(Constraint):\n    \"\"\"Ensure an input is of value `None`\"\"\"\n    def __call__(self, value):\n        if value is None or isinstance(value, _NoneDeprecated):\n            return None\n        else:\n            raise ValueError(\"value must be `None`\")\n\n    def short_description(self):\n        return 'None'\n\n    def long_description(self):\n        return 'value must be `None`'\n\n\nclass EnsureCallable(Constraint):\n    \"\"\"Ensure an input is a callable object\"\"\"\n    def __call__(self, value):\n        if hasattr(value, '__call__'):\n            return value\n        else:\n            raise ValueError(\"value must be a callable\")\n\n    def short_description(self):\n        return 'callable'\n\n    def long_description(self):\n        return 'value must be a callable'\n\n\nclass EnsureChoice(Constraint):\n    \"\"\"Ensure an input is element of a set of possible values\"\"\"\n\n    def __init__(self, *values):\n        \"\"\"\n        Parameters\n        ----------\n        *values\n           Possible accepted values.\n        \"\"\"\n        self._allowed = values\n        super(EnsureChoice, self).__init__()\n\n    def __call__(self, value):\n        if value not in self._allowed:\n            raise ValueError(f\"value {value} is not one of {self._allowed}\")\n        return value\n\n    def long_description(self):\n        return 'value must be one of [CMD: %s CMD][PY: %s PY]' % (\n            str(tuple(i for i in self._allowed if i is not None)),\n            str(self._allowed)\n        )\n\n    def short_description(self):\n        return '{%s}' % ', '.join([repr(c) for c in self._allowed])\n\n\nclass EnsureKeyChoice(EnsureChoice):\n    \"\"\"Ensure value under a key in an input is in a set of possible values\"\"\"\n\n    def __init__(self, key, values):\n        \"\"\"\n        Parameters\n        ----------\n        key : str\n          The to-be-tested values are looked up under the given key in\n          a dict-like input object.\n        values : tuple\n           Possible accepted values.\n        \"\"\"\n        self._key = key\n        super(EnsureKeyChoice, self).__init__(*values)\n\n    def __call__(self, value):\n        if self._key not in value:\n            raise ValueError(\"value not dict-like\")\n        super(EnsureKeyChoice, self).__call__(value[self._key])\n        return value\n\n    def long_description(self):\n        return \"value in '%s' must be one of %s\" % (self._key, str(self._allowed),)\n\n    def short_description(self):\n        return '%s:{%s}' % (self._key, ', '.join([str(c) for c in self._allowed]))\n\n\nclass EnsureRange(Constraint):\n    \"\"\"Ensure an input is within a particular range\n\n    No type checks are performed.\n    \"\"\"\n    def __init__(self, min=None, max=None):\n        \"\"\"\n        Parameters\n        ----------\n        min\n            Minimal value to be accepted in the range\n        max\n            Maximal value to be accepted in the range\n        \"\"\"\n        self._min = min\n        self._max = max\n        super(EnsureRange, self).__init__()\n\n    def __call__(self, value):\n        if self._min is not None:\n            if value < self._min:\n                raise ValueError(\"value must be at least %s\" % (self._min,))\n        if self._max is not None:\n            if value > self._max:\n                raise ValueError(\"value must be at most %s\" % (self._max,))\n        return value\n\n    def long_description(self):\n        min_str = '-inf' if self._min is None else str(self._min)\n        max_str = 'inf' if self._max is None else str(self._max)\n        return 'value must be in range [%s, %s]' % (min_str, max_str)\n\n    def short_description(self):\n        return None\n\n\nclass _MultiConstraint(Constraint):\n    \"\"\"Helper class to override the description methods to reported\n    multiple constraints\n    \"\"\"\n    def _get_description(self, attr):\n        cs = [\n            getattr(c, attr)()\n            for c in self.constraints\n            if hasattr(c, attr)\n        ]\n        cs = [c for c in cs if c is not None]\n        doc = ' or '.join(cs)\n        if len(cs) > 1:\n            return f'({doc})'\n        else:\n            return doc\n\n    def long_description(self):\n        return self._get_description('long_description')\n\n    def short_description(self):\n        return self._get_description('short_description')\n\n\nclass AltConstraints(_MultiConstraint):\n    \"\"\"Logical OR for constraints.\n\n    An arbitrary number of constraints can be given. They are evaluated in the\n    order in which they were specified. The value returned by the first\n    constraint that does not raise an exception is the global return value.\n\n    Documentation is aggregated for all alternative constraints.\n    \"\"\"\n    def __init__(self, *constraints):\n        \"\"\"\n        Parameters\n        ----------\n        *constraints\n           Alternative constraints\n        \"\"\"\n        super(AltConstraints, self).__init__()\n        self.constraints = [EnsureNone() if c is None else c for c in constraints]\n\n    def __or__(self, other):\n        if isinstance(other, AltConstraints):\n            self.constraints.extend(other.constraints)\n        else:\n            self.constraints.append(other)\n        return self\n\n    def __call__(self, value):\n        e_list = []\n        for c in self.constraints:\n            try:\n                return c(value)\n            except Exception as e:\n                e_list.append(e)\n        raise ValueError(f\"{value} does not match any alternative: \"\n                         f\"{self.constraints} {e_list}\")\n\n\nclass Constraints(_MultiConstraint):\n    \"\"\"Logical AND for constraints.\n\n    An arbitrary number of constraints can be given. They are evaluated in the\n    order in which they were specified. The return value of each constraint is\n    passed an input into the next. The return value of the last constraint\n    is the global return value. No intermediate exceptions are caught.\n\n    Documentation is aggregated for all constraints.\n    \"\"\"\n    def __init__(self, *constraints):\n        \"\"\"\n        Parameters\n        ----------\n        *constraints\n           Constraints all of which must be satisfied\n        \"\"\"\n        super(Constraints, self).__init__()\n        self.constraints = [EnsureNone() if c is None else c for c in constraints]\n\n    def __and__(self, other):\n        if isinstance(other, Constraints):\n            self.constraints.extend(other.constraints)\n        else:\n            self.constraints.append(other)\n        return self\n\n    def __call__(self, value):\n        for c in (self.constraints):\n            value = c(value)\n        return value\n\n\nconstraint_spec_map = {\n    'float': EnsureFloat(),\n    'int': EnsureInt(),\n    'bool': EnsureBool(),\n    'str': EnsureStr(),\n}\n\n\ndef expand_constraint_spec(spec):\n    \"\"\"Helper to translate literal constraint specs into functional ones\n\n    e.g. 'float' -> EnsureFloat()\n    \"\"\"\n    if spec is None or hasattr(spec, '__call__'):\n        return spec\n    else:\n        try:\n            return constraint_spec_map[spec]\n        except KeyError:\n            raise ValueError(\"unsupported constraint specification '%r'\" % (spec,))\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** EnsureListOf and EnsureTupleOf Incorrectly Split String Inputs into Characters

**Description:**
When using `EnsureListOf` or `EnsureTupleOf` with a string input, the functions mistakenly break the string into individual characters instead of treating the entire string as a single element.

**Example Code:**
```python
c = EnsureListOf(str)
result = c('a1 b2')
print(result)  # Expected: ['a1 b2'], Got: ['a', '1', ' ', 'b', '2']

c = EnsureTupleOf(str)
result = c('a1 b2')
print(result)  # Expected: ('a1 b2',), Got: ('a', '1', ' ', 'b', '2')
```

**Expected Behavior:**
- `EnsureListOf(str)` should return `['a1 b2']` when given `'a1 b2'` as input.
- `EnsureTupleOf(str)` should return `('a1 b2',)` when given `'a1 b2'` as input.

**Actual Behavior:**
- `EnsureListOf(str)` returns `['a', '1', ' ', 'b', '2']`.
- `EnsureTupleOf(str)` returns `('a', '1', ' ', 'b', '2')`.

This behavior is unintended and causes the functions to misinterpret string inputs by splitting them into individual characters rather than treating the entire string as a single list or tuple element.
[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### datalad/support/constraints.py
# emacs: -*- mode: python; py-indent-offset: 4; tab-width: 4; indent-tabs-mode: nil -*-
# ex: set sts=4 ts=4 sw=4 et:
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
#
#   See COPYING file distributed along with the datalad package for the
#   copyright and license terms.
#
# ## ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ### ##
"""Helper for parameter validation, documentation and conversion"""

__docformat__ = 'restructuredtext'

import re


def _strip_typerepr(s):
    """Strip away <class '...'> and <type '...'> decorations for docstrings
    """
    return re.sub(r"<(class|type) '(\S+)'>", r'\2', s)


def _type_str(t):
    """Get string human-readable representation of a data type

    If type (t) is given as a tuple, assume ability to choose any of the
    listed types, so those types listing get joined with |
    """
    if isinstance(t, tuple):
        s = ' or '.join(map(_type_str, t))
        return ("(%s)" % s) if len(t) > 1 else s
    return _strip_typerepr(str(t))


class _NoneDeprecated:
    """A helper construct to ease migrations for option=None.

    Should be instantiated, so it gains `__repr__` and thus would render nicely
    in docs etc.
    """
    def __repr__(self):
        return 'None(DEPRECATED)'


NoneDeprecated = _NoneDeprecated()


class Constraint(object):
    """Base class for input value conversion/validation.

    These classes are also meant to be able to generate appropriate
    documentation on an appropriate parameter value.
    """

    # TODO: __str__ and/or __repr__ for every one of them

    def __repr__(self):
        """Rudimentary repr to avoid default scary to the user Python repr"""
        return "constraint:%s" % self.short_description()

    def __and__(self, other):
        return Constraints(self, other)

    def __or__(self, other):
        return AltConstraints(self, other)

    def __call__(self, value):
        # do any necessary checks or conversions, potentially catch exceptions
        # and generate a meaningful error message
        raise NotImplementedError("abstract class")

    def long_description(self):
        # return meaningful docs or None
        # used as a comprehensive description in the parameter list
        return self.short_description()

    def short_description(self):
        # return meaningful docs or None
        # used as a condensed primer for the parameter lists
        raise NotImplementedError("abstract class")


class EnsureDType(Constraint):
    """Ensure that an input (or several inputs) are of a particular data type.
    """
    # TODO extend to support numpy-like dtype specs, e.g. 'int64'
    # in addition to functors
    def __init__(self, dtype):
        """
        Parameters
        ----------
        dtype : functor
        """
        self._dtype = dtype

    def __call__(self, value):
        if hasattr(value, '__iter__') and \
                not (isinstance(value, (bytes, str))):
            return list(map(self._dtype, value))
        else:
            return self._dtype(value)

    def short_description(self):
        return _type_str(self._dtype)

    def long_description(self):
        return "value must be convertible to type '%s'" % self.short_description()


class EnsureInt(EnsureDType):
    """Ensure that an input (or several inputs) are of a data type 'int'.
    """
    def __init__(self):
        """Initializes EnsureDType with int"""
        EnsureDType.__init__(self, int)


class EnsureFloat(EnsureDType):
    """Ensure that an input (or several inputs) are of a data type 'float'.
    """
    def __init__(self):
        """Initializes EnsureDType with float"""
        EnsureDType.__init__(self, float)


class EnsureListOf(Constraint):
    """Ensure that an input is a list of a particular data type
    """
    def __init__(self, dtype):
        """
        Parameters
        ----------
        dtype : functor
        """
        self._dtype = dtype
        super(EnsureListOf, self).__init__()

    def __call__(self, value):
        return list(map(self._dtype, value))

    def short_description(self):
        return 'list(%s)' % _type_str(self._dtype)

    def long_description(self):
        return "value must be convertible to %s" % self.short_description()


class EnsureTupleOf(Constraint):
    """Ensure that an input is a tuple of a particular data type
    """
    def __init__(self, dtype):
        """
        Parameters
        ----------
        dtype : functor
        """
        self._dtype = dtype
        super(EnsureTupleOf, self).__init__()

    def __call__(self, value):
        return tuple(map(self._dtype, value))

    def short_description(self):
        return 'tuple(%s)' % _type_str(self._dtype)

    def long_description(self):
        return "value must be convertible to %s" % self.short_description()


class EnsureBool(Constraint):
    """Ensure that an input is a bool.

    A couple of literal labels are supported, such as:
    False: '0', 'no', 'off', 'disable', 'false'
    True: '1', 'yes', 'on', 'enable', 'true'
    """
    def __call__(self, value):
        if isinstance(value, bool):
            return value
        elif isinstance(value, (bytes, str)):
            value = value.lower()
            if value in ('0', 'no', 'off', 'disable', 'false'):
                return False
            elif value in ('1', 'yes', 'on', 'enable', 'true'):
                return True
        raise ValueError(
            "value '{}' must be convertible to boolean".format(
                value))

    def long_description(self):
        return 'value must be convertible to type bool'

    def short_description(self):
        return 'bool'


class EnsureStr(Constraint):
    """Ensure an input is a string.

    No automatic conversion is attempted.
    """
    def __init__(self, min_len=0):
        """
        Parameters
        ----------
        min_len: int, optional
           Minimal length for a string.
        """
        assert(min_len >= 0)
        self._min_len = min_len
        super(EnsureStr, self).__init__()

    def __call__(self, value):
        if not isinstance(value, (bytes, str)):
            # do not perform a blind conversion ala str(), as almost
            # anything can be converted and the result is most likely
            # unintended
            raise ValueError("%s is not a string" % repr(value))
        if len(value) < self._min_len:
            raise ValueError("%r is shorter than of minimal length %d"
                             % (value, self._min_len))
        return value

    def long_description(self):
        return 'value must be a string'

    def short_description(self):
        return 'str'


class EnsureStrPrefix(EnsureStr):
    """Ensure an input is a string that starts with a given prefix.
    """
    def __init__(self, prefix):
        """
        Parameters
        ----------
        prefix : str
           Mandatory prefix.
        """
        self._prefix = prefix
        super().__init__()

    def __call__(self, value):
        super().__call__(value)
        if not value.startswith(self._prefix):
            raise ValueError("%r does not start with '%s'"
                             % (value, self._prefix))
        return value

    def long_description(self):
        return "value must start with '{}'".format(self._prefix)

    def short_description(self):
        return '{}...'.format(self._prefix)


class EnsureNone(Constraint):
    """Ensure an input is of value `None`"""
    def __call__(self, value):
        if value is None or isinstance(value, _NoneDeprecated):
            return None
        else:
            raise ValueError("value must be `None`")

    def short_description(self):
        return 'None'

    def long_description(self):
        return 'value must be `None`'


class EnsureCallable(Constraint):
    """Ensure an input is a callable object"""
    def __call__(self, value):
        if hasattr(value, '__call__'):
            return value
        else:
            raise ValueError("value must be a callable")

    def short_description(self):
        return 'callable'

    def long_description(self):
        return 'value must be a callable'


class EnsureChoice(Constraint):
    """Ensure an input is element of a set of possible values"""

    def __init__(self, *values):
        """
        Parameters
        ----------
        *values
           Possible accepted values.
        """
        self._allowed = values
        super(EnsureChoice, self).__init__()

    def __call__(self, value):
        if value not in self._allowed:
            raise ValueError(f"value {value} is not one of {self._allowed}")
        return value

    def long_description(self):
        return 'value must be one of [CMD: %s CMD][PY: %s PY]' % (
            str(tuple(i for i in self._allowed if i is not None)),
            str(self._allowed)
        )

    def short_description(self):
        return '{%s}' % ', '.join([repr(c) for c in self._allowed])


class EnsureKeyChoice(EnsureChoice):
    """Ensure value under a key in an input is in a set of possible values"""

    def __init__(self, key, values):
        """
        Parameters
        ----------
        key : str
          The to-be-tested values are looked up under the given key in
          a dict-like input object.
        values : tuple
           Possible accepted values.
        """
        self._key = key
        super(EnsureKeyChoice, self).__init__(*values)

    def __call__(self, value):
        if self._key not in value:
            raise ValueError("value not dict-like")
        super(EnsureKeyChoice, self).__call__(value[self._key])
        return value

    def long_description(self):
        return "value in '%s' must be one of %s" % (self._key, str(self._allowed),)

    def short_description(self):
        return '%s:{%s}' % (self._key, ', '.join([str(c) for c in self._allowed]))


class EnsureRange(Constraint):
    """Ensure an input is within a particular range

    No type checks are performed.
    """
    def __init__(self, min=None, max=None):
        """
        Parameters
        ----------
        min
            Minimal value to be accepted in the range
        max
            Maximal value to be accepted in the range
        """
        self._min = min
        self._max = max
        super(EnsureRange, self).__init__()

    def __call__(self, value):
        if self._min is not None:
            if value < self._min:
                raise ValueError("value must be at least %s" % (self._min,))
        if self._max is not None:
            if value > self._max:
                raise ValueError("value must be at most %s" % (self._max,))
        return value

    def long_description(self):
        min_str = '-inf' if self._min is None else str(self._min)
        max_str = 'inf' if self._max is None else str(self._max)
        return 'value must be in range [%s, %s]' % (min_str, max_str)

    def short_description(self):
        return None


class _MultiConstraint(Constraint):
    """Helper class to override the description methods to reported
    multiple constraints
    """
    def _get_description(self, attr):
        cs = [
            getattr(c, attr)()
            for c in self.constraints
            if hasattr(c, attr)
        ]
        cs = [c for c in cs if c is not None]
        doc = ' or '.join(cs)
        if len(cs) > 1:
            return f'({doc})'
        else:
            return doc

    def long_description(self):
        return self._get_description('long_description')

    def short_description(self):
        return self._get_description('short_description')


class AltConstraints(_MultiConstraint):
    """Logical OR for constraints.

    An arbitrary number of constraints can be given. They are evaluated in the
    order in which they were specified. The value returned by the first
    constraint that does not raise an exception is the global return value.

    Documentation is aggregated for all alternative constraints.
    """
    def __init__(self, *constraints):
        """
        Parameters
        ----------
        *constraints
           Alternative constraints
        """
        super(AltConstraints, self).__init__()
        self.constraints = [EnsureNone() if c is None else c for c in constraints]

    def __or__(self, other):
        if isinstance(other, AltConstraints):
            self.constraints.extend(other.constraints)
        else:
            self.constraints.append(other)
        return self

    def __call__(self, value):
        e_list = []
        for c in self.constraints:
            try:
                return c(value)
            except Exception as e:
                e_list.append(e)
        raise ValueError(f"{value} does not match any alternative: "
                         f"{self.constraints} {e_list}")


class Constraints(_MultiConstraint):
    """Logical AND for constraints.

    An arbitrary number of constraints can be given. They are evaluated in the
    order in which they were specified. The return value of each constraint is
    passed an input into the next. The return value of the last constraint
    is the global return value. No intermediate exceptions are caught.

    Documentation is aggregated for all constraints.
    """
    def __init__(self, *constraints):
        """
        Parameters
        ----------
        *constraints
           Constraints all of which must be satisfied
        """
        super(Constraints, self).__init__()
        self.constraints = [EnsureNone() if c is None else c for c in constraints]

    def __and__(self, other):
        if isinstance(other, Constraints):
            self.constraints.extend(other.constraints)
        else:
            self.constraints.append(other)
        return self

    def __call__(self, value):
        for c in (self.constraints):
            value = c(value)
        return value


constraint_spec_map = {
    'float': EnsureFloat(),
    'int': EnsureInt(),
    'bool': EnsureBool(),
    'str': EnsureStr(),
}


def expand_constraint_spec(spec):
    """Helper to translate literal constraint specs into functional ones

    e.g. 'float' -> EnsureFloat()
    """
    if spec is None or hasattr(spec, '__call__'):
        return spec
    else:
        try:
            return constraint_spec_map[spec]
        except KeyError:
            raise ValueError("unsupported constraint specification '%r'" % (spec,))

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user