← Files Dataiku HeadlessARCHIVED FILE

dataiku_mcp/tools/utils/validation.py

5.15 KB · Nov 11, 2026 · 12:58 UTC

↓ Download file

See the change to this file →

# Copyright 2026 Dataiku SAS
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Shared validation helpers for tool modules."""

import re
from pathlib import PurePosixPath
from urllib.parse import urlsplit

_HEX_COLOR_PATTERN = re.compile(r"^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")


def require_non_empty_string(value: str, field_name: str) -> str:
    try:
        cleaned = value.strip()
    except Exception as exc:
        raise ValueError(f"'{field_name}' must be a non-empty string") from exc
    if not cleaned:
        raise ValueError(f"'{field_name}' must be a non-empty string")
    return cleaned


def require_non_empty_strings(values: list[str], field_name: str) -> list[str]:
    return [
        require_non_empty_string(value, f"{field_name}[{index}]")
        for index, value in enumerate(values)
    ]


def require_positive_int(value: int, field_name: str) -> int:
    if value is True or value is False:
        raise ValueError(f"'{field_name}' must be >= 1")
    try:
        if value < 1:
            raise ValueError(f"'{field_name}' must be >= 1")
    except TypeError as exc:
        raise ValueError(f"'{field_name}' must be >= 1") from exc
    return value


def require_non_negative_int(value: int, field_name: str) -> int:
    if value is True or value is False:
        raise ValueError(f"'{field_name}' must be >= 0")
    try:
        if value < 0:
            raise ValueError(f"'{field_name}' must be >= 0")
    except TypeError as exc:
        raise ValueError(f"'{field_name}' must be >= 0") from exc
    return value


def require_int_at_least(value: int, field_name: str, minimum: int) -> int:
    cleaned = require_positive_int(value, field_name)
    if cleaned < minimum:
        raise ValueError(f"'{field_name}' must be >= {minimum}")
    return cleaned


def require_int_in_range(
    value: int, field_name: str, minimum: int, maximum: int
) -> int:
    """Require an int within an inclusive range, for bounded inline waits and caps."""
    cleaned = require_int_at_least(value, field_name, minimum)
    if cleaned > maximum:
        raise ValueError(f"'{field_name}' must be <= {maximum}")
    return cleaned


def require_non_empty_list(values: list, field_name: str) -> list:
    if len(values) < 1:
        raise ValueError(f"'{field_name}' must be a non-empty list")
    return values


def require_allowed_value(value: str, field_name: str, allowed_values: set[str]) -> str:
    if value not in allowed_values:
        allowed = sorted(allowed_values)
        raise ValueError(
            f"Invalid '{field_name}': '{value}'. Allowed values: {allowed}"
        )
    return value


def require_fraction(value: float, field_name: str) -> float:
    if value is True or value is False:
        raise ValueError(f"'{field_name}' must be > 0 and < 1")
    try:
        cleaned = float(value)
    except (TypeError, ValueError) as exc:
        raise ValueError(f"'{field_name}' must be > 0 and < 1") from exc
    if cleaned <= 0 or cleaned >= 1:
        raise ValueError(f"'{field_name}' must be > 0 and < 1")
    return cleaned


def require_hex_color(value: str, field_name: str) -> str:
    cleaned = require_non_empty_string(value, field_name)
    if not _HEX_COLOR_PATTERN.match(cleaned):
        raise ValueError(
            f"'{field_name}' must be a hex color in '#RGB' or '#RRGGBB' format"
        )
    return cleaned


def require_git_repository_url(value: str) -> str:
    """Accept remote Git URLs without inline secrets or local filesystem access."""
    value = require_non_empty_string(value, "repository_url")
    if any(character.isspace() for character in value):
        raise ValueError("'repository_url' must not contain whitespace")
    if re.fullmatch(r"[\w.][\w.-]*@[\w.-]+:[^?#]+", value):
        return value
    try:
        parsed = urlsplit(value)
        valid = (
            parsed.scheme in {"https", "http", "ssh", "git"}
            and parsed.hostname
            and parsed.path not in {"", "/"}
            and not parsed.query
            and not parsed.fragment
            and parsed.password is None
            and (parsed.username is None or parsed.scheme == "ssh")
        )
    except ValueError:
        valid = False
    if not valid:
        raise ValueError(
            "'repository_url' must be a remote Git URL without passwords, tokens, "
            "query parameters, or fragments. Configure credentials in Dataiku."
        )
    return value


def require_relative_posix_path(value: str, field_name: str) -> str:
    value = require_non_empty_string(value, field_name)
    path = PurePosixPath(value)
    if path.is_absolute() or ".." in path.parts or "\\" in value:
        raise ValueError(f"'{field_name}' must be a relative path without '..'")
    return value

SHA-256: 5a532dad44aa0e677992d9499eeec9245d0875d8a360394925664e7f16453915