#
# Copyright 2021-2022 DataRobot, Inc. and its affiliates.
#
# All rights reserved.
#
# DataRobot, Inc.
#
# This is proprietary source code of DataRobot, Inc. and its
# affiliates.
#
# Released under the terms of DataRobot Tool and Utility Agreement.
# pylint: disable=too-many-lines
from __future__ import annotations

import json
from typing import Any, cast, List, Optional, TYPE_CHECKING, Union

from requests import Response

from datarobot.models.api_object import APIObject
from datarobot.utils.pagination import unpaginate

from .trafarets import (
    UserBlueprint_,
    UserBlueprintAddToMenuResponse_,
    UserBlueprintCatalogSearchItem_,
    UserBlueprintSharedRolesListResponseValidator_,
    UserBlueprintsInputTypesResponse_,
    UserBlueprintsValidateTaskParametersResponse_,
    UserBlueprintTasksResponse_,
    UserBlueprintValidationResponse_,
)

if TYPE_CHECKING:
    from mypy_extensions import TypedDict

    class UserBlueprintsHexColumnNameLookupEntryDict(TypedDict):
        colname: str
        hex: str
        project_id: Optional[str]


DEFAULT_BATCH_SIZE = 100


class HumanReadable:
    """
    A simple mixin to provide human readable class representation.
    """

    def __repr__(self) -> str:
        result = []

        for key, value in self.__dict__.items():
            try:
                converted_value = value
                if isinstance(value, str):
                    converted_value = value.encode("utf-8").decode("utf-8")
                result += [f"    {key}={converted_value.__repr__()},"]
            except (UnicodeEncodeError, UnicodeDecodeError):
                pass
        return f"{self.__class__.__name__}" + "(\n{}\n)".format("\n".join(result))


class CompareWithJSON:
    def as_json(self) -> Any:
        return json.loads(json.dumps(self, default=lambda i: getattr(i, "__dict__", str(i))))

    def __eq__(self, other: Any) -> bool:
        if not isinstance(other, CompareWithJSON):
            return False

        return cast(bool, self.as_json() == other.as_json())


class UserBlueprint(APIObject, HumanReadable, CompareWithJSON):
    """
    A representation of a blueprint which may be modified by the user,
    saved to a user's AI Catalog, trained on projects, and shared with others.

    It is recommended to install the python library called ``datarobot_bp_workshop``,
    available via ``pip``, for the best experience when building blueprints.

    Please refer to ``http://blueprint-workshop.datarobot.com`` for tutorials,
    examples, and other documentation.

    Parameters
    ----------
    blender: bool
        Whether the blueprint is a blender.
    blueprint_id: string
        The deterministic id of the blueprint, based on its content.
    custom_task_version_metadata: list[list[string]], Optional
        An association of custom entity ids and task ids.
    diagram: string
        The diagram used by the UI to display the blueprint.
    features: list[string]
        A list of the names of tasks used in the blueprint.
    features_text: string
        A description of the blueprint via the names of tasks used.
    hex_column_name_lookup: list[UserBlueprintsHexColumnNameLookupEntry], Optional
        A lookup between hex values and data column names used in the blueprint.
    icons: list[int]
        The icon(s) associated with the blueprint.
    insights: string
        An indication of the insights generated by the blueprint.
    is_time_series: bool (Default=False)
        Whether the blueprint contains time-series tasks.
    model_type: string
        The generated or provided title of the blueprint.
    project_id: string, Optional
        The id of the project the blueprint was originally created with, if applicable.
    reference_model: bool (Default=False)
        Whether the blueprint is a reference model.
    shap_support: bool (Default=False)
        Whether the blueprint supports shapley additive explanations.
    supported_target_types: list[enum('binary', 'multiclass', 'multilabel', 'nonnegative',
    'regression', 'unsupervised', 'unsupervisedclustering')]
        The list of supported targets of the current blueprint.
    supports_gpu: bool (Default=False)
        Whether the blueprint supports execution on the GPU.
    user_blueprint_id: string
        The unique id associated with the user blueprint.
    user_id: string
        The id of the user who owns the blueprint.
    blueprint: list[dict] or list[UserBlueprintTask], Optional
        The representation of a directed acyclic graph defining a pipeline of data through tasks
        and a final estimator.
    vertex_context: list[VertexContextItem], Optional
        Info about, warnings about, and errors with a specific vertex in the blueprint.
    blueprint_context: VertexContextItemMessages
        Warnings and errors which may describe or summarize warnings or errors in the blueprint's
        vertices
    """

    _path = "userBlueprints/{userBlueprintId}/"
    _converter = UserBlueprint_

    def __init__(
        self,
        blender: bool,
        blueprint_id: str,
        diagram: str,
        features: List[str],
        features_text: str,
        icons: List[int],
        insights: str,
        model_type: str,
        supported_target_types,
        user_blueprint_id: str,
        user_id: str,
        is_time_series: bool = False,
        reference_model: bool = False,
        shap_support: bool = False,
        supports_gpu: bool = False,
        blueprint=None,
        custom_task_version_metadata=None,
        hex_column_name_lookup: Optional[List[UserBlueprintsHexColumnNameLookupEntryDict]] = None,
        project_id: Optional[str] = None,
        vertex_context: Optional[List[VertexContextItem]] = None,
        blueprint_context: Optional[VertexContextItemMessages] = None,
        **kwargs: Any,
    ) -> None:
        self.blender = blender
        self.blueprint = _init_list_of_class_or_dict(blueprint, UserBlueprintTask)
        self.blueprint_id = blueprint_id
        self.custom_task_version_metadata = custom_task_version_metadata
        self.diagram = diagram
        self.features = features
        self.features_text = features_text
        self.hex_column_name_lookup = _init_list_of_class_or_dict(
            hex_column_name_lookup, UserBlueprintHexColumnNameLookupEntry
        )
        self.icons = icons
        self.insights = insights
        self.is_time_series = is_time_series
        self.model_type = model_type
        self.project_id = project_id
        self.reference_model = reference_model
        self.shap_support = shap_support
        self.supported_target_types = supported_target_types
        self.supports_gpu = supports_gpu
        self.user_blueprint_id = user_blueprint_id
        self.user_id = user_id
        self.blueprint_context = _init_class_or_dict(blueprint_context, VertexContextItemMessages)
        self.vertex_context = _init_list_of_class_or_dict(vertex_context, VertexContextItem)

    @classmethod
    def list(
        cls, limit: int = 100, offset: int = 0, project_id: Optional[str] = None
    ) -> List[UserBlueprint]:
        """
        Fetch a list of the user blueprints the current user created

        Parameters
        ----------
        limit: int (Default=100)
            The max number of results to return.
        offset: int (Default=0)
            The number of results to skip (for pagination).
        project_id: string, Optional
            The id of the project, used to filter for original project_id.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        list[UserBlueprint]
        """
        path = "userBlueprints/"
        params = dict(
            offset=offset,
            limit=limit or DEFAULT_BATCH_SIZE,
            project_id=None if project_id is None else str(project_id),
        )
        if limit == 0:
            data = list(unpaginate(path, params, cls._client))
        else:
            data = cls._client.get(path, params=params).json()["data"]
        return [cls.from_server_data(a) for a in data]

    @classmethod
    def get(cls, user_blueprint_id: str, project_id: Optional[str] = None) -> UserBlueprint:
        """
        Retrieve a user blueprint

        Parameters
        ----------
        user_blueprint_id: string
            Used to identify a specific user-owned blueprint.
        project_id: string (optional, default is None)
            String representation of ObjectId for a given project. Used to validate selected
            columns in the user blueprint.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprint
        """
        response = cls._client.get(
            cls._path.format(userBlueprintId=user_blueprint_id),
            params=dict(edit_mode=True, decompressed_blueprint=True, project_id=project_id),
        )
        return cls.from_server_data(response.json())

    @classmethod
    def create(
        cls,
        blueprint,
        model_type: Optional[str] = None,
        project_id: Optional[str] = None,
        save_to_catalog: bool = True,
    ) -> UserBlueprint:
        """
        Create a user blueprint

        Parameters
        ----------
        blueprint: list[dict] or list[UserBlueprintTask]
            A list of tasks in the form of dictionaries which define a blueprint.
        model_type: string, Optional
            The title to give to the blueprint.
        project_id: string, Optional
            The project associated with the blueprint. Necessary in the event of project specific
            tasks, such as column selection tasks.
        save_to_catalog: bool, (Default=True)
            Whether the blueprint being created should be saved to the catalog.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprint
        """
        _path = "userBlueprints/"
        response = cls._client.post(
            _path,
            data=dict(
                blueprint=[b.as_json() if not isinstance(b, dict) else b for b in blueprint],
                project_id=None if project_id is None else str(project_id),
                model_type=model_type,
                decompressed_blueprint=True,
                save_to_catalog=save_to_catalog,
            ),
            keep_attrs=["value"],
        )
        return cls.from_server_data(response.json())

    @classmethod
    def create_from_custom_task_version_id(
        cls,
        custom_task_version_id: str,
        save_to_catalog: bool = True,
        description: Optional[str] = None,
    ) -> UserBlueprint:
        """
        Create a user blueprint with a single custom task version

        Parameters
        ----------
        custom_task_version_id: string
            Id of custom task version from which the user blueprint is created
        save_to_catalog: bool, (Default=True)
            Whether the blueprint being created should be saved to the catalog
        description: string (Default=None)
            The description for the user blueprint that will be created from the
            custom task version.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprint
        """
        _path = "userBlueprints/fromCustomTaskVersionId/"
        response = cls._client.post(
            _path,
            data=dict(
                custom_task_version_id=str(custom_task_version_id),
                save_to_catalog=save_to_catalog,
                description=description,
                decompressed_blueprint=True,
            ),
        )
        return cls.from_server_data(response.json())

    @classmethod
    def clone_project_blueprint(
        cls,
        blueprint_id: str,
        project_id: str,
        model_type: Optional[str] = None,
        save_to_catalog: bool = True,
    ) -> UserBlueprint:
        """
        Clone a blueprint from a project.

        Parameters
        ----------
        blueprint_id: string
            The id associated with the blueprint to create the user blueprint from.
        model_type: string, Optional
            The title to give to the blueprint.
        project_id: string
            The id of the project which the blueprint to copy comes from.
        save_to_catalog: bool, (Default=True)
            Whether the blueprint being created should be saved to the catalog.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprint
        """
        _path = "userBlueprints/fromBlueprintId/"
        response = cls._client.post(
            _path,
            data=dict(
                blueprint_id=blueprint_id,
                project_id=str(project_id),
                model_type=model_type,
                decompressed_blueprint=True,
                save_to_catalog=save_to_catalog,
            ),
        )
        return cls.from_server_data(response.json())

    @classmethod
    def clone_user_blueprint(
        cls,
        user_blueprint_id: str,
        model_type: Optional[str] = None,
        project_id: Optional[str] = None,
        save_to_catalog: bool = True,
    ) -> UserBlueprint:
        """
        Clone a user blueprint.

        Parameters
        ----------
        model_type: string, Optional
            The title to give to the blueprint.
        project_id: string, Optional
            String representation of ObjectId for a given project. Used to validate selected
            columns in the user blueprint.
        user_blueprint_id: string
            The id of the existing user blueprint to copy.
        save_to_catalog: bool, (Default=True)
            Whether the blueprint being created should be saved to the catalog.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprint
        """
        _path = "userBlueprints/fromUserBlueprintId/"

        response = cls._client.post(
            _path,
            data=dict(
                user_blueprint_id=str(user_blueprint_id),
                project_id=None if project_id is None else str(project_id),
                model_type=model_type,
                decompressed_blueprint=True,
                save_to_catalog=save_to_catalog,
            ),
        )
        return cls.from_server_data(response.json())

    @classmethod
    def update(
        cls,
        blueprint,
        user_blueprint_id: str,
        model_type: Optional[str] = None,
        project_id: Optional[str] = None,
        include_project_id_if_none: bool = False,
    ) -> UserBlueprint:
        """
        Update a user blueprint

        Parameters
        ----------
        blueprint: list(dict) or list(UserBlueprintTask)
            A list of tasks in the form of dictionaries which define a blueprint.
            If None, will not be passed.
        model_type: string, Optional
            The title to give to the blueprint. If None, will not be passed.
        project_id: string, Optional
            The project associated with the blueprint. Necessary in the event of project specific
            tasks, such as column selection tasks.
            If None, will not be passed. To explicitly pass None, pass True to
            `include_project_id_if_none` (useful if unlinking a blueprint from a project)
        user_blueprint_id: string
            Used to identify a specific user-owned blueprint.
        include_project_id_if_none: bool (Default=False)
            Allows project_id to be passed as None, instead of ignored.
            If set to False, will not pass `project_id` in the API request if it is set to None.
            If True, the project id will be passed even if it is set to None.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprint
        """
        kwargs = {}
        if blueprint is not None:
            kwargs["blueprint"] = [b.as_json() if not isinstance(b, dict) else b for b in blueprint]
        if model_type is not None:
            kwargs["model_type"] = model_type
        if project_id is not None or include_project_id_if_none:
            kwargs["project_id"] = None if project_id is None else str(project_id)
        response = cls._client.patch(
            cls._path.format(userBlueprintId=user_blueprint_id),
            data=dict(decompressed_blueprint=True, **kwargs),
            keep_attrs=["value", "project_id"],
        )
        return cls.from_server_data(response.json())

    @classmethod
    def delete(cls, user_blueprint_id: str) -> Response:
        """
        Delete a user blueprint, specified by the `userBlueprintId`.

        Parameters
        ----------
        user_blueprint_id: string
            Used to identify a specific user-owned blueprint.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        :class:`requests.models.Response`
        """
        return cast(
            Response, cls._client.delete(cls._path.format(userBlueprintId=user_blueprint_id))
        )

    @classmethod
    def get_input_types(cls) -> UserBlueprintAvailableInput:
        """
        Retrieve the input types which can be used with User Blueprints.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprintAvailableInput
        """
        return UserBlueprintAvailableInput.get_input_types()

    @classmethod
    def add_to_project(
        cls,
        project_id: str,
        user_blueprint_ids: Union[str, List[str]],
    ) -> UserBlueprintAddToProjectMenu:
        """
        Add a list of user blueprints, by id, to a specified (by id) project's repository.

        Parameters
        ----------
        project_id: string
            The projectId of the project for the repository to add the specified user blueprints
            to.
        user_blueprint_ids: list(string) or string
            The ids of the user blueprints to add to the specified project's repository.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprintAddToProjectMenu
        """
        user_blueprint_ids = (
            [str(u) for u in user_blueprint_ids]
            if isinstance(user_blueprint_ids, list)
            else [str(user_blueprint_ids)]
        )
        return UserBlueprintAddToProjectMenu.add_to_project(
            project_id=str(project_id), user_blueprint_ids=user_blueprint_ids
        )

    @classmethod
    def get_available_tasks(
        cls,
        project_id: Optional[str] = None,
        user_blueprint_id: Optional[str] = None,
    ) -> UserBlueprintAvailableTasks:
        """
        Retrieve the available tasks, organized into categories, which can be used to create or
        modify User Blueprints.

        Parameters
        ----------
        project_id: string, Optional
        user_blueprint_id: string, Optional

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprintAvailableTasks
        """
        return UserBlueprintAvailableTasks.get_available_tasks(
            project_id=None if project_id is None else str(project_id),
            user_blueprint_id=None if user_blueprint_id is None else str(user_blueprint_id),
        )

    @classmethod
    def validate_task_parameters(
        cls,
        output_method: str,
        task_code: str,
        task_parameters,
        project_id: Optional[str] = None,
    ) -> UserBlueprintValidateTaskParameters:
        """
        Validate that each value assigned to specified task parameters are valid.

        Parameters
        ----------
        output_method: enum('P', 'Pm', 'S', 'Sm', 'T', 'TS')
            The method representing how the task will output data.
        task_code: string
            The task code representing the task to validate parameter values.
        task_parameters: list(UserBlueprintTaskParameterValidationRequestParamItem)
            A list of task parameters and proposed values to be validated.
        project_id: string (optional, default is None)
            The projectId representing the project where this user blueprint is edited.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprintValidateTaskParameters
        """
        return UserBlueprintValidateTaskParameters.validate_task_parameters(
            output_method=output_method,
            task_code=task_code,
            task_parameters=task_parameters,
            project_id=project_id,
        )

    @classmethod
    def list_shared_roles(
        cls,
        user_blueprint_id: str,
        limit: int = 100,
        offset: int = 0,
        id: Optional[str] = None,
        name: Optional[str] = None,
        share_recipient_type: Optional[str] = None,
    ) -> List[UserBlueprintSharedRolesResponseValidator]:
        """
        Get a list of users, groups and organizations that have an access to this user blueprint

        Parameters
        ----------
        id: str, Optional
            Only return the access control information for a organization, group or user with this
            ID.
        limit: int (Default=100)
            At most this many results are returned.
        name: string, Optional
            Only return the access control information for a organization, group or user with this
            name.
        offset: int (Default=0)
            This many results will be skipped.
        share_recipient_type: enum('user', 'group', 'organization'), Optional
            Describes the recipient type, either user, group, or organization.
        user_blueprint_id: str
            Used to identify a specific user-owned blueprint.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        list[UserBlueprintSharedRolesResponseValidator]
        """
        return UserBlueprintSharingListController.list_shared_roles(
            user_blueprint_id=user_blueprint_id,
            limit=limit,
            offset=offset,
            id=id,
            name=name,
            share_recipient_type=share_recipient_type,
        )

    @classmethod
    def validate_blueprint(
        cls, blueprint, project_id: Optional[str] = None
    ) -> List[VertexContextItem]:
        """Validate a user blueprint and return information about the inputs expected and outputs
        provided by each task.

        Parameters
        ----------
        blueprint: list(dict) or list(UserBlueprintTask)
            The representation of a directed acyclic graph defining a pipeline of data through tasks
            and a final estimator.
        project_id: string (optional, default is None)
            The projectId representing the project where this user blueprint is edited.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        list[VertexContextItem]
        """
        return UserBlueprintValidate.validate_blueprint(blueprint, project_id=project_id)

    @classmethod
    def update_shared_roles(
        cls,
        user_blueprint_id: str,
        roles: List[
            Union[GrantAccessControlWithUsernameValidator, GrantAccessControlWithIdValidator]
        ],
    ) -> Response:
        """
        Share a user blueprint with a user, group, or organization

        Parameters
        ----------
        user_blueprint_id: str
            Used to identify a specific user-owned blueprint.
        roles: list(or(GrantAccessControlWithUsernameValidator, GrantAccessControlWithIdValidator))
            Array of GrantAccessControl objects., up to maximum 100 objects.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        :class:`requests.models.Response`
        """
        return UserBlueprintSharingUpdateController.update_shared_roles(
            user_blueprint_id=user_blueprint_id, roles=roles
        )

    @classmethod
    def search_catalog(
        cls,
        search: Optional[str] = None,
        tag: Optional[str] = None,
        limit: int = 100,
        offset: int = 0,
        owner_user_id: Optional[str] = None,
        owner_username: Optional[str] = None,
        order_by: str = "-created",
    ) -> UserBlueprintCatalogSearch:
        """
        Fetch a list of the user blueprint catalog entries the current user has access to
        based on an optional search term, tags, owner user info, or sort order.

        Parameters
        ----------
        search: string, Optional.
            A value to search for in the dataset's name, description, tags, column names,
            categories, and latest error. The search is case insensitive. If no value is provided
            for this parameter, or if the empty string is used, or if the string contains only
            whitespace, no filtering will be done. Partial matching is performed on dataset name
            and description fields while all other fields will only match if the search matches the
            whole value exactly.

        tag: string, Optional.
            If provided, the results will be filtered to include only items with the specified tag.

        limit: int, Optional. (default: 0), at most this many results are returned. To specify no
            limit, use 0. The default may change and a maximum limit may be imposed without notice.

        offset: int, Optional. (default: 0), this many results will be skipped.

        owner_user_id: string, Optional.
            Filter results to those owned by one or more owner identified by UID.

        owner_username: string,  Optional.
            Filter results to those owned by one or more owner identified by username.

        order_by: string, Optional. Defaults to '-created'.
            Sort order which will be applied to catalog list, valid options are "catalogName",
            "originalName", "description", "created", and "relevance". For all options other
            than relevance, you may prefix the attribute name with a dash to sort
            in descending order. e.g. orderBy='-catalogName'.
        """
        return UserBlueprintCatalogSearch.search_catalog(
            search=search,
            tag=tag,
            limit=limit,
            offset=offset,
            owner_user_id=owner_user_id,
            owner_username=owner_username,
            order_by=order_by,
        )


class UserBlueprintCatalogSearch(APIObject, HumanReadable, CompareWithJSON):
    """
    An APIObject representing a user blueprint catalog entry the current
    user has access to based on an optional search term and/or tags.

    Parameters
    ----------
    id: str
        The ID of the catalog entry linked to the user blueprint.

    catalog_name: str
        The name of the user blueprint.

    creator: str
        The name of the user that created the user blueprint.

    user_blueprint_id: str
        The ID of the user blueprint.

    description: str, Optional (Default=None)
        The description of the user blueprint.

    last_modifier_name: str, Optional (Default=None)
        The name of the user that last modified the user blueprint.
    """

    _path = "catalogItems/"
    _converter = UserBlueprintCatalogSearchItem_

    def __init__(
        self,
        id: str,
        catalog_name: str,
        info_creator_full_name: str,
        user_blueprint_id: str,
        description: Optional[str] = None,
        last_modifier_full_name: Optional[str] = None,
        **kwargs: Any,
    ) -> None:
        self.id = id
        self.catalog_name = catalog_name
        self.creator = info_creator_full_name
        self.user_blueprint_id = user_blueprint_id
        self.description = description
        self.last_modifier_name = last_modifier_full_name

    @classmethod
    def search_catalog(
        cls,
        search: Optional[str] = None,
        tag: Optional[str] = None,
        limit: int = 100,
        offset: int = 0,
        owner_user_id: Optional[str] = None,
        owner_username: Optional[str] = None,
        order_by: str = "-created",
    ) -> List[UserBlueprintCatalogSearch]:
        """
        Fetch a list of the user blueprint catalog entries the current user has access to
        based on an optional search term, tags, owner user info, or sort order.

        Parameters
        ----------
        search: string, Optional.
            A value to search for in the dataset's name, description, tags, column names,
            categories, and latest error. The search is case insensitive. If no value is provided
            for this parameter, or if the empty string is used, or if the string contains only
            whitespace, no filtering will be done. Partial matching is performed on dataset name
            and description fields while all other fields will only match if the search matches the
            whole value exactly.

        tag: string, Optional.
            If provided, the results will be filtered to include only items with the specified tag.

        limit: int, Optional. (default: 0), at most this many results are returned. To specify no
            limit, use 0. The default may change and a maximum limit may be imposed without notice.

        offset: int, Optional. (default: 0), this many results will be skipped.

        owner_user_id: string, Optional.
            Filter results to those owned by one or more owner identified by UID.

        owner_username: string,  Optional.
            Filter results to those owned by one or more owner identified by username.

        order_by: string, Optional. Defaults to '-created'.
            Sort order which will be applied to catalog list, valid options are "catalogName",
            "originalName", "description", "created", and "relevance". For all options other
            than relevance, you may prefix the attribute name with a dash to sort
            in descending order. e.g. orderBy='-catalogName'.
        """
        params = dict(
            search_for=search,
            tag=tag,
            type="user_blueprint",
            limit=limit or DEFAULT_BATCH_SIZE,
            offset=offset,
            owner_user_id=owner_user_id,
            owner_username=owner_username,
            order_by=order_by,
        )
        if limit == 0:
            data = list(unpaginate(cls._path, params, cls._client))
        else:
            data = cls._client.get(cls._path, params=params).json()["data"]

        return [cls.from_server_data(a) for a in data]


class UserBlueprintAvailableInput(APIObject, HumanReadable, CompareWithJSON):
    """
    Retrieve the input types which can be used with User Blueprints.

    Parameters
    ----------
    input_types: list(UserBlueprintsInputType)
        A list of associated pairs of an input types and their human-readable names.
    """

    _path = "userBlueprintsInputTypes/"
    _converter = UserBlueprintsInputTypesResponse_

    def __init__(self, input_types: List[UserBlueprintsInputType], **kwargs: Any) -> None:
        self.input_types = _init_list_of_class_or_dict(input_types, UserBlueprintsInputType)

    @classmethod
    def get_input_types(cls) -> UserBlueprintAvailableInput:
        """
        Retrieve the input types which can be used with User Blueprints.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprintAvailableInput
        """
        response = cls._client.get(cls._path)
        return cls.from_server_data(response.json())


class UserBlueprintAddToProjectMenu(APIObject, HumanReadable, CompareWithJSON):
    """
    Add a list of user blueprints, by id, to a specified (by id) project's repository.

    Parameters
    ----------
    added_to_menu: list(UserBlueprintAddedToMenuItem)
        The list of userBlueprintId and blueprintId pairs representing blueprints successfully
        added to the project repository.
    not_added_to_menu: list(UserBlueprintNotAddedToMenuItem)
        The list of userBlueprintId and error message representing blueprints which failed to be
        added to the project repository.
    message: string
        A success message or a list of reasons why the list of blueprints could not be added
        to the project repository.
    """

    _path = "userBlueprintsProjectBlueprints/"
    _converter = UserBlueprintAddToMenuResponse_

    def __init__(
        self,
        added_to_menu: List[UserBlueprintAddedToMenuItem],
        not_added_to_menu: List[UserBlueprintAddedToMenuItem] = None,
        message: Optional[str] = None,
        **kwargs: Any,
    ) -> None:
        self.added_to_menu = _init_list_of_class_or_dict(
            added_to_menu, UserBlueprintAddedToMenuItem
        )
        self.not_added_to_menu = _init_list_of_class_or_dict(
            not_added_to_menu, UserBlueprintNotAddedToMenuItem
        )
        self.message = message

    @classmethod
    def add_to_project(
        cls, project_id: str, user_blueprint_ids: List[str]
    ) -> UserBlueprintAddToProjectMenu:
        """
        Add a list of user blueprints, by id, to a specified (by id) project's repository.

        Parameters
        ----------
        project_id: string
            The projectId of the project for the repository to add the specified user blueprints
            to.
        user_blueprint_ids: list(string)
            The ids of the user blueprints to add to the specified project's repository.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprintAddToProjectMenu
        """
        response = cls._client.post(
            cls._path,
            data=dict(
                project_id=str(project_id), user_blueprint_ids=[str(u) for u in user_blueprint_ids]
            ),
        )
        return cls.from_server_data(response.json())


class UserBlueprintValidateTaskParameters(APIObject, HumanReadable, CompareWithJSON):
    """
    Validate that each value assigned to specified task parameters are valid.

    Parameters
    ----------
    errors: list(UserBlueprintsValidateTaskParameter)
        A list of the task parameters, their proposed values, and messages describing why each is
        not valid.
    """

    _path = "userBlueprintsTaskParameters/"
    _converter = UserBlueprintsValidateTaskParametersResponse_

    def __init__(self, errors: List[UserBlueprintsValidateTaskParameter], **kwargs: Any) -> None:
        self.errors = _init_list_of_class_or_dict(errors, UserBlueprintsValidateTaskParameter)

    @classmethod
    def validate_task_parameters(
        cls,
        output_method: str,
        task_code: str,
        task_parameters: List[UserBlueprintTaskParameterValidationRequestParamItem],
        project_id: Optional[str] = None,
    ) -> UserBlueprintValidateTaskParameters:
        """
        Validate that each value assigned to specified task parameters are valid.

        Parameters
        ----------
        output_method: enum('P', 'Pm', 'S', 'Sm', 'T', 'TS')
            The method representing how the task will output data.
        task_code: string
            The task code representing the task to validate parameter values.
        task_parameters: list(UserBlueprintTaskParameterValidationRequestParamItem)
            A list of task parameters and proposed values to be validated.
        project_id: string (optional, default is None)
            The projectId representing the project where this user blueprint is edited.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprintValidateTaskParameters
        """
        response = cls._client.post(
            cls._path,
            data=dict(
                task_code=task_code,
                output_method=output_method,
                task_parameters=[
                    t.as_json() if not isinstance(t, dict) else t for t in task_parameters
                ],
                project_id=project_id,
            ),
        )
        return cls.from_server_data(response.json())


class UserBlueprintValidate(APIObject, HumanReadable, CompareWithJSON):
    """
    Validate the structure of the blueprint and that blueprint inputs/outputs are valid. Also return
    information about task data inputs and outputs.

    Parameters
    ----------
    vertex_context: list(VertexContextItem)
        Information about blueprint vertices, including warning and error messages.
    blueprint_context: VertexContextItemMessages
        Warnings and errors which may describe or summarize warnings or errors in the blueprint's
        vertices
    """

    _path = "userBlueprintsValidations/"
    _converter = UserBlueprintValidationResponse_

    def __init__(
        self,
        vertex_context: List[VertexContextItem],
        blueprint_context: VertexContextItemMessages,
        **kwargs: Any,
    ) -> None:
        self.blueprint_context = _init_class_or_dict(blueprint_context, VertexContextItemMessages)
        self.vertex_context = _init_list_of_class_or_dict(vertex_context, VertexContextItem)

    @classmethod
    def validate_blueprint(
        cls,
        blueprint,
        project_id: Optional[str] = None,
    ) -> UserBlueprintValidate:
        """Validate the structure of the blueprint and that blueprint inputs/outputs are valid.
        Also return information about task data inputs and outputs.
        Parameters
        ----------
        blueprint: list(dict) or list(UserBlueprintTask)
            The representation of a directed acyclic graph defining a pipeline of data through tasks
            and a final estimator.
        project_id: string, Optional (Default=None)
            The project associated with the blueprint. Necessary in the event of project specific
            tasks, such as column selection tasks.
        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status
        Returns
        -------
        UserBlueprintValidate
        """
        response = cls._client.post(
            cls._path,
            data=dict(
                blueprint=[b.as_json() if not isinstance(b, dict) else b for b in blueprint],
                project_id=project_id,
            ),
        )
        return cls.from_server_data(response.json())


class UserBlueprintAvailableTasks(APIObject, HumanReadable, CompareWithJSON):
    """
    Retrieve the available tasks, organized into categories, which can be used to create or modify
    User Blueprints.

    Parameters
    ----------
    categories: list(UserBlueprintTaskCategoryItem)
        A list of the available task categories, sub-categories, and tasks.
    tasks: list(UserBlueprintTaskLookupEntry)
        A list of task codes and their task definitions.
    """

    _path = "userBlueprintsTasks/"
    _converter = UserBlueprintTasksResponse_

    def __init__(
        self,
        categories: List[UserBlueprintTaskCategoryItem],
        tasks: List[UserBlueprintTaskLookupEntry],
        **kwargs: Any,
    ) -> None:
        self.categories = _init_list_of_class_or_dict(categories, UserBlueprintTaskCategoryItem)
        self.tasks = _init_list_of_class_or_dict(tasks, UserBlueprintTaskLookupEntry)

    @classmethod
    def get_available_tasks(
        cls, project_id: Optional[str] = None, user_blueprint_id: Optional[str] = None
    ) -> UserBlueprintAvailableTasks:
        """
        Retrieve the available tasks, organized into categories, which can be used to create or
        modify User Blueprints.

        Parameters
        ----------
        project_id: string, Optional
        user_blueprint_id: string, Optional

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        UserBlueprintAvailableTasks
        """
        response = cls._client.get(
            cls._path,
            params=dict(
                project_id=None if project_id is None else str(project_id),
                user_blueprint_id=None if user_blueprint_id is None else str(user_blueprint_id),
            ),
        )
        return cls.from_server_data(response.json())


class UserBlueprintValidationRequest(HumanReadable, CompareWithJSON):
    """
    Blueprint and optional project id for validation.

    Attributes
    ----------
    blueprint: list(dict) or list(UserBlueprintTask)
         The blueprint in JSON format.
    project_id: string
         The project associated with the blueprint. Necessary for
         project-specific tasks, such as column selection tasks.
    """

    def __init__(
        self,
        blueprint,
        project_id: Optional[str] = None,
    ) -> None:
        self.project_id = project_id
        self.blueprint = blueprint


class UserBlueprintValidationResponse(HumanReadable, CompareWithJSON):
    """
    Vertex context / validation information and warnings or errors.

    Attributes
    ----------
    vertex_context: list(VertexContextItem)
         Info, warnings, and errors about each vertex in the blueprint.
    blueprint_context: VertexContextItemMessages
        Warnings and errors which may describe or summarize warnings or errors in the blueprint's
        vertices
    """

    def __init__(
        self,
        vertex_context: List[VertexContextItem],
        blueprint_context: VertexContextItemMessages,
    ) -> None:
        self.vertex_context = vertex_context
        self.blueprint_context = blueprint_context


class UserBlueprintSharingListController(APIObject, HumanReadable, CompareWithJSON):
    """
    Get a list of users, groups and organizations that have an access to this user blueprint

    Parameters
    ----------
    count: int, Optional
        Number of items returned on this page.
    next: url or null
        URL pointing to the next page (if null, there is no next page).
    previous: url or null
        URL pointing to the previous page (if null, there is no previous page).
    data: list(UserBlueprintSharedRolesResponseValidator)
        A list of SharedRoles objects.
    total_count: int
        The total number of items across all pages.
    """

    _path = "userBlueprints/{userBlueprintId}/sharedRoles/"
    _converter = UserBlueprintSharedRolesListResponseValidator_

    def __init__(
        self,
        data: List[UserBlueprintSharedRolesResponseValidator],
        **kwargs: Any,
    ) -> None:
        self.data = _init_list_of_class_or_dict(data, UserBlueprintSharedRolesResponseValidator)

    @classmethod
    def list_shared_roles(
        cls,
        user_blueprint_id: str,
        limit: int = 100,
        offset: int = 0,
        id: Optional[str] = None,
        name: Optional[str] = None,
        share_recipient_type: Optional[str] = None,
    ) -> List[UserBlueprintSharedRolesResponseValidator]:
        """
        Get a list of users, groups and organizations that have an access to this user blueprint

        Parameters
        ----------
        id: str, Optional
            Only return the access control information for a organization, group or user with this
            ID.
        limit: int (Default=100)
            At most this many results are returned.
        name: string, Optional
            Only return the access control information for a organization, group or user with this
            name.
        offset: int (Default=0)
            This many results will be skipped.
        share_recipient_type: enum('user', 'group', 'organization'), Optional
            Describes the recipient type, either user, group, or organization.
        user_blueprint_id: str
            Used to identify a specific user-owned blueprint.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        list(UserBlueprintSharedRolesResponseValidator)
        """
        path = cls._path.format(userBlueprintId=user_blueprint_id)
        params = dict(
            offset=offset,
            limit=limit or DEFAULT_BATCH_SIZE,
            name=name,
            id=id,
            share_recipient_type=share_recipient_type,
        )
        if limit == 0:
            data = list(unpaginate(path, params, cls._client))
        else:
            data = cls._client.get(path, params=params).json()["data"]
        return cls.from_server_data(dict(data=data)).data


class UserBlueprintSharingUpdateController(
    APIObject, HumanReadable, CompareWithJSON
):  # pylint: disable=missing-class-docstring
    _path = "userBlueprints/{userBlueprintId}/sharedRoles/"

    @classmethod
    def update_shared_roles(
        cls,
        user_blueprint_id: str,
        roles: List[
            Union[GrantAccessControlWithUsernameValidator, GrantAccessControlWithIdValidator]
        ],
    ) -> Response:
        """
        Share a user blueprint with a user, group, or organization

        Parameters
        ----------
        user_blueprint_id: str
            Used to identify a specific user-owned blueprint.
        roles: list(or(GrantAccessControlWithUsernameValidator, GrantAccessControlWithIdValidator))
            Array of GrantAccessControl objects., up to maximum 100 objects.

        Raises
        ------
        datarobot.errors.ClientError
            if the server responded with 4xx status
        datarobot.errors.ServerError
            if the server responded with 5xx status

        Returns
        -------
        :class:`requests.models.Response`
        """
        roles_json = []
        for role in roles:
            if not isinstance(
                role,
                (dict, GrantAccessControlWithUsernameValidator, GrantAccessControlWithIdValidator),
            ):
                raise TypeError(
                    "'roles' must be a list of one of: dict, "
                    "GrantAccessControlWithUsernameValidator, GrantAccessControlWithIdValidator"
                )
            roles_json.append(role if isinstance(role, dict) else role.as_json())
        return cast(
            Response,
            cls._client.patch(
                cls._path.format(userBlueprintId=user_blueprint_id),
                data=dict(operation="updateRoles", roles=roles_json),
            ),
        )


class UserBlueprintSharedRolesResponseValidator(HumanReadable, CompareWithJSON):
    """
    A list of SharedRoles objects.

    Parameters
    ----------
    share_recipient_type: enum('user', 'group', 'organization')
        Describes the recipient type, either user, group, or organization.
    role: str, one of enum('CONSUMER', 'EDITOR', 'OWNER')
        The role of the org/group/user on this dataset or "NO_ROLE" for removing access when used
        with route to modify access.
    id: str
        The ID of the recipient organization, group or user.
    name: string
        The name of the recipient organization, group or user.
    """

    def __init__(self, id: str, name: str, role, share_recipient_type: str, **kwargs: Any) -> None:
        self.share_recipient_type = share_recipient_type
        self.role = role
        self.id = id
        self.name = name


class GrantAccessControlWithIdValidator(HumanReadable, CompareWithJSON):
    """

    Parameters
    ----------
    share_recipient_type: enum('user', 'group', 'organization')
        Describes the recipient type, either user, group, or organization.
    role: string
        The role of the recipient on this entity. One of OWNER, USER, OBSERVER.
    id: str
        The ID of the recipient.
    """

    def __init__(
        self,
        id: str,
        role: str,
        share_recipient_type: str,
        **kwargs: Any,
    ) -> None:
        self.share_recipient_type = share_recipient_type
        self.role = role
        self.id = id


class GrantAccessControlWithUsernameValidator(HumanReadable, CompareWithJSON):
    """

    Parameters
    ----------
    share_recipient_type: enum('user', 'group', 'organization')
        Describes the recipient type, either user, group, or organization.
    role: string
        The role of the recipient on this entity. One of OWNER, USER, OBSERVER.
    username: string
        Username of the user to update the access role for.
    """

    def __init__(
        self,
        role: str,
        share_recipient_type: str,
        username: str,
        **kwargs: Any,
    ) -> None:
        self.share_recipient_type = share_recipient_type
        self.role = role
        self.username = username


class UserBlueprintHexColumnNameLookupEntry(HumanReadable, CompareWithJSON):
    """
    A lookup between hex values and data column names used in the blueprint.

    Parameters
    ----------
    colname: string
        The name of the column.
    hex: string
        A safe hex representation of the column name.
    project_id: string, Optional
        The id of the project from which the column name originates.
    """

    def __init__(
        self, colname: str, hex: str, project_id: Optional[str] = None, **kwargs: Any
    ) -> None:
        self.colname = colname
        self.hex = hex
        self.project_id = project_id


class UserBlueprintTask(HumanReadable, CompareWithJSON):
    """
    A task (i.e. vertex) in a blueprint

    Parameters
    ----------
    task_id: string
        The identifier of a task / vertex in the blueprint.
    task_data: UserBlueprintTaskData
        The data defining the task / vertex in the blueprint.
    """

    def __init__(self, task_data: UserBlueprintTaskData, task_id: str, **kwargs: Any) -> None:
        self.task_id = task_id
        self.task_data = _init_class_or_dict(task_data, UserBlueprintTaskData)


class UserBlueprintTaskData(HumanReadable, CompareWithJSON):
    """
    The data defining the task / vertex in the blueprint.

    Parameters
    ----------
    inputs: list(string)
        The ids or input data types which will be inputs to the task.
    output_method: string
        The method which the task will use to produce output.
    output_method_parameters: list(ParamValuePair)
        The parameters which further define how output will be produced.
    task_code: string
        The unique code representing the python class which will be instantiated and executed.
    task_parameters: list(ParamValuePair)
        The parameters which further define the behavior of the task.
    x_transformations: list(ParamValuePair)
        Transformations to apply to the input data before fitting or predicting.
    y_transformations: list(ParamValuePair)
        Transformations to apply to the input target before fitting or predicting.
    """

    def __init__(
        self,
        inputs: List[str],
        output_method: str,
        output_method_parameters: List[ParamValuePair],
        task_code: str,
        task_parameters: List[ParamValuePair],
        x_transformations: List[ParamValuePair],
        y_transformations: List[ParamValuePair],
        **kwargs: Any,
    ) -> None:
        self.inputs = inputs
        self.output_method = output_method
        self.output_method_parameters = _init_list_of_class_or_dict(
            output_method_parameters, ParamValuePair
        )
        self.task_code = task_code
        self.task_parameters = _init_list_of_class_or_dict(task_parameters, ParamValuePair)
        self.x_transformations = _init_list_of_class_or_dict(x_transformations, ParamValuePair)
        self.y_transformations = _init_list_of_class_or_dict(y_transformations, ParamValuePair)


class VertexContextItem(HumanReadable, CompareWithJSON):
    """
    Info about, warnings about, and errors with a specific vertex in the blueprint.

    Parameters
    ----------
    task_id: string
        The id associated with a specific vertex in the blueprint.
    information: VertexContextItemInfo
    messages: VertexContextItemMessages
    """

    def __init__(
        self,
        information: VertexContextItemInfo,
        messages: VertexContextItemMessages,
        task_id: str,
        **kwargs: Any,
    ) -> None:
        """
        Parameters
        ----------
        task_id: string
            The id associated with a specific vertex in the blueprint.
        information: dict
        messages: dict
        """
        self.task_id = task_id
        self.information = _init_class_or_dict(information, VertexContextItemInfo)
        self.messages = _init_class_or_dict(messages, VertexContextItemMessages)


class VertexContextItemMessages(HumanReadable, CompareWithJSON):
    """

    Parameters
    ----------
    errors: list(string), Optional
        Errors with a specific vertex in the blueprint. Execution of the vertex is expected to
        fail.
    warnings: list(string), Optional
        Warnings about a specific vertex in the blueprint. Execution of the vertex may fail or
        behave unexpectedly.
    """

    def __init__(
        self,
        errors: Optional[List[str]] = None,
        warnings: Optional[List[str]] = None,
        **kwargs: Any,
    ) -> None:
        self.errors = errors
        self.warnings = warnings


class VertexContextItemInfo(HumanReadable, CompareWithJSON):
    """

    Parameters
    ----------
    inputs: list(string)
        A specification of requirements of the inputs of the vertex.
    outputs: list(string)
        A specification of expectations of the output of the vertex.
    """

    def __init__(self, inputs: List[str], outputs: List[str], **kwargs: Any) -> None:
        self.inputs = inputs
        self.outputs = outputs


class ParamValuePair(CompareWithJSON):
    """
    Transformations to apply to the input target before fitting or predicting.

    Parameters
    ----------
    param: string
        The name of a field associated with the value.
    value: any or list(any)
        Any value.
    """

    def __init__(
        self, param: str, value: Optional[Union[Any, List[Any]]] = None, **kwargs: Any
    ) -> None:
        self.param = param
        self.value = value

    def __repr__(self) -> str:
        return f"({self.param}={self.value})"


class UserBlueprintsInputType(HumanReadable, CompareWithJSON):
    """
    A list of associated pairs of an input types and their human-readable names.

    Parameters
    ----------
    type: string
        The unique identifier of an input type.
    name: string
        The human-readable name of an input type.
    """

    def __init__(self, name: str, type: str, **kwargs: Any) -> None:
        self.type = type
        self.name = name


class UserBlueprintAddedToMenuItem(HumanReadable, CompareWithJSON):
    """
    A userBlueprintId and blueprintId pair representing blueprints successfully added to
    the project repository.

    Parameters
    ----------
    blueprint_id: string
        The blueprintId representing the blueprint which was added to the project repository.
    user_blueprint_id: string
        The userBlueprintId associated with the blueprintId added to the project repository.
    """

    def __init__(self, blueprint_id: str, user_blueprint_id: str, **kwargs: Any) -> None:
        self.blueprint_id = blueprint_id
        self.user_blueprint_id = user_blueprint_id


class UserBlueprintNotAddedToMenuItem(HumanReadable, CompareWithJSON):
    """
    A userBlueprintId and error pair representing blueprints which failed to be added to
    the project repository.

    Parameters
    ----------
    error: string
        The error message representing why the blueprint was not added to the project repository.
    user_blueprint_id: string
        The userBlueprintId associated with the blueprint which was not added to the project
        repository.
    """

    def __init__(self, blueprint_id: str, user_blueprint_id: str, **kwargs: Any) -> None:
        self.blueprint_id = blueprint_id
        self.user_blueprint_id = user_blueprint_id


class UserBlueprintsValidateTaskParameter(HumanReadable, CompareWithJSON):
    """
    A list of the task parameters, their proposed values, and messages describing why each is not
    valid.

    Parameters
    ----------
    message: string
        The description of the issue with the proposed task parameter value.
    param_name: string
        The name of the validated task parameter.
    value: any or list(any)
        The invalid value proposed for the validated task parameter.
    """

    def __init__(
        self,
        message: str,
        param_name: str,
        value: Optional[Union[Any, List[Any]]] = None,
        **kwargs: Any,
    ) -> None:
        self.message = message
        self.param_name = param_name
        self.value = value


class UserBlueprintTaskParameterValidationRequestParamItem(HumanReadable, CompareWithJSON):
    """
    A list of task parameters and proposed values to be validated.

    Parameters
    ----------
    param_name: string
        The name of the task parameter to be validated.
    new_value: any or list(any)
        The proposed value for the task parameter.
    """

    def __init__(
        self,
        param_name: str,
        new_value: Optional[Union[Any, List[Any]]] = None,
        **kwargs: Any,
    ) -> None:
        self.param_name = param_name
        self.new_value = new_value


class UserBlueprintTaskLookupEntry(HumanReadable, CompareWithJSON):
    """
    A list of task codes and their task definitions.

    Parameters
    ----------
    task_code: string
        The unique code which represents the task to be constructed and executed
    task_definition: UserBlueprintTaskDefinition
        A definition of a task in terms of label, arguments, description, and other metadata.
    """

    def __init__(
        self, task_code: str, task_definition: UserBlueprintTaskDefinition, **kwargs: Any
    ) -> None:
        self.task_code = task_code
        self.task_definition = _init_class_or_dict(task_definition, UserBlueprintTaskDefinition)


class UserBlueprintTaskDefinition(HumanReadable, CompareWithJSON):
    """
    A definition of a task in terms of label, arguments, description, and other metadata.

    Parameters
    ----------
    task_code: string
        The unique code which represents the task to be constructed and executed
    label: string
        The generic / default title or label for the task.
    description: string
        A description of the task.
    arguments: list(UserBlueprintTaskArgument)
        A list of definitions of each argument which can be set for the task.
    categories: list(string)
        The categories which the task is in.
    colnames_and_types: list(ColnameAndType), Optional
        The column names, their types, and their hex representation, available in the specified
        project for the task.
    icon: int
        The integer representing the id to be displayed when the blueprint is trained.
    output_methods: list(string)
        The methods which the task can use to produce output.
    time_series_only: bool
        Whether the task can only be used with time series projects.
    url: dict or string or TaskDocumentationUrl
        The URL of the documentation of the task.
    valid_inputs: list(string)
    is_custom_task: bool, Optional
        Whether the task is custom code written by the user.
    custom_task_id: string or null, Optional
        The id of the custom task, if it is a custom task.
    custom_task_versions: list(UserBlueprintTaskCustomTaskMetadata), Optional
        Metadata for all of the custom task's versions.
    supports_scoring_code: bool
        Whether Scoring Code can be generated for the task.
    """

    def __init__(
        self,
        arguments: List[UserBlueprintTaskArgument],
        categories: List[str],
        description: str,
        icon: int,
        label: str,
        output_methods: List[str],
        task_code: str,
        time_series_only: bool,
        url,
        valid_inputs: List[str],
        colnames_and_types: Optional[List[ColnameAndType]] = None,
        custom_task_id: Optional[str] = None,
        custom_task_versions: Optional[List[UserBlueprintTaskCustomTaskMetadata]] = None,
        is_custom_task: Optional[bool] = None,
        supports_scoring_code: bool = False,
        **kwargs: Any,
    ) -> None:
        self.task_code = task_code
        self.label = label
        self.description = description
        self.arguments = _init_list_of_class_or_dict(arguments, UserBlueprintTaskArgument)
        self.categories = categories
        self.colnames_and_types = _init_list_of_class_or_dict(colnames_and_types, ColnameAndType)
        self.icon = icon
        self.output_methods = output_methods
        self.time_series_only = time_series_only
        self.url = _init_class_or_dict(url, TaskDocumentationUrl)
        self.valid_inputs = valid_inputs
        self.is_custom_task = is_custom_task
        self.custom_task_id = custom_task_id
        self.custom_task_versions = _init_list_of_class_or_dict(
            custom_task_versions, UserBlueprintTaskCustomTaskMetadata
        )
        self.supports_scoring_code = supports_scoring_code


class UserBlueprintTaskCustomTaskMetadata(HumanReadable, CompareWithJSON):
    """
    Metadata for all of the custom task's versions.

    Parameters
    ----------
    id: string
        Id of the custom task version. The ID can be latest_<task_id> which implies to use the
        latest version of that custom task.
    version_major: int
        Major version of the custom task.
    version_minor: int
        Minor version of the custom task.
    label: string
        The name of the custom task version.
    """

    def __init__(
        self, id: str, label: str, version_major: int, version_minor: int, **kwargs: Any
    ) -> None:
        self.id = id
        self.version_major = version_major
        self.version_minor = version_minor
        self.label = label


class TaskDocumentationUrl(HumanReadable, CompareWithJSON):
    """
    The url of the documentation, if one exists.

    Parameters
    ----------
    documentation: string, Optional
        The URL of the documentation of the task.
    """

    def __init__(self, documentation: Optional[str] = None, **kwargs: Any) -> None:
        self.documentation = documentation
        if self.documentation is None:
            self.documentation = kwargs.get("data_robot _model _docs")


class ColnameAndType(HumanReadable, CompareWithJSON):
    """
    The column names, their types, and their hex representation, available in the specified project
    for the task.

    Parameters
    ----------
    hex: string
        A safe hex representation of the column name.
    colname: string
        The column name.
    type: string
        The data type of the column.
    """

    def __init__(self, colname: str, hex: str, type: str, **kwargs: Any) -> None:
        self.hex = hex
        self.colname = colname
        self.type = type


class UserBlueprintTaskArgument(HumanReadable, CompareWithJSON):
    """
    A list of definitions of each argument which can be set for the task.

    Parameters
    ----------
    key: string
        The unique key of the argument
    argument: UserBlueprintTaskArgumentDefinition
        The definition of a task argument, used to specify a certain aspect of the task.
    """

    def __init__(
        self, argument: UserBlueprintTaskArgumentDefinition, key: str, **kwargs: Any
    ) -> None:
        self.key = key
        self.argument = _init_class_or_dict(argument, UserBlueprintTaskArgumentDefinition)


class UserBlueprintTaskArgumentDefinition(HumanReadable, CompareWithJSON):
    """
    The definition of a task argument, used to specify a certain aspect of the task.

    Parameters
    ----------
    name: string
        The name of the argument.
    type: string
        The type of the argument (e.g. "int", "float", "select", "intgrid", "multi", etc.)
    default: any or list(any), Optional
        The default value of the argument.
    values: any or list(any) or dict
        The possible values of the argument, which may be a range or list, or dictionary of ranges
        or lists keyed by type.
    tunable: bool, Optional
        Whether the argument is tunable by the end-user.
    recommended: any or list(any), Optional
        The recommended value, based on frequently used values.
    """

    def __init__(
        self,
        name: str,
        type: str,
        values,
        default: Optional[Union[Any, List[Any]]] = None,
        recommended: Optional[Union[Any, List[Any]]] = None,
        tunable: Optional[bool] = None,
        **kwargs: Any,
    ) -> None:
        self.name = name
        self.type = type
        self.default = default
        self.values = values
        self.tunable = tunable
        self.recommended = recommended


class UserBlueprintTaskCategoryItem(HumanReadable, CompareWithJSON):
    """
    A list of the available task categories, sub-categories, and tasks.

    Parameters
    ----------
    name: string
        The name of the category.
    task_codes: list(string)
        A list of task codes representing the tasks in this category.
    subcategories: list(UserBlueprintTaskCategoryItem), Optional
        A list of the available task category items.
    """

    def __init__(
        self,
        name: str,
        task_codes: List[str],
        subcategories: Optional[List[UserBlueprintTaskCategoryItem]] = None,
        **kwargs: Any,
    ) -> None:
        self.name = name
        self.task_codes = task_codes
        self.subcategories = _init_list_of_class_or_dict(
            subcategories, UserBlueprintTaskCategoryItem
        )


def _init_class_or_dict(item, class_):
    """
    Given None, a dictionary or an instance of `class_` return it
    as the instantiated form of `class_` or None.

    Parameters
    ----------
    item: dict or class_ instance or None
    class_: Type

    Returns
    -------
    class_ instance or None
    """
    return class_(**item) if isinstance(item, dict) else item


def _init_list_of_class_or_dict(items, class_):
    """
    Given a list of ( None, a dictionary or an instance of `class_` )
    return a list of their instantiated form of `class_` or as None.

    Parameters
    ----------
    items: list(dict or class_ instance or None)
    class_: Type

    Returns
    -------
    list(class_ instance) or None
    """
    return None if items is None else [class_(**a) if isinstance(a, dict) else a for a in items]
