コード例 #1
0
ファイル: display.py プロジェクト: beer-garden/brewtils
def resolve_form(form=None, base_dir=None):
    # type: (Union[None, dict, list, str], str) -> Optional[dict]
    """Resolve a form attribute

    Returns:
        Dictionary that fully describes a form
    """
    if form is None or isinstance(form, dict):
        return form
    elif isinstance(form, list):
        return {"type": "fieldset", "items": form}
    elif isinstance(form, six.string_types):
        try:
            if form.startswith("http"):
                return _load_from_url(form)
            elif form.startswith("/") or form.startswith("."):
                return json.loads(_load_from_path(form, base_dir=base_dir))
            else:
                raise PluginParamError(
                    "Form was not a definition, file path, or URL")
        except Exception as ex:
            six.raise_from(PluginParamError("Error resolving form: %s" % ex),
                           ex)
    else:
        raise PluginParamError(
            "Schema was not a definition, file path, or URL")
コード例 #2
0
def _resolve_display_modifiers(wrapped,
                               command_name,
                               schema=None,
                               form=None,
                               template=None):
    def _load_from_url(url):
        return json.loads(requests.get(url).text)

    def _load_from_path(path):
        current_dir = os.path.dirname(inspect.getfile(wrapped))
        file_path = os.path.abspath(os.path.join(current_dir, path))

        with open(file_path, 'r') as definition_file:
            return definition_file.read()

    resolved = {}

    for key, value in {
            'schema': schema,
            'form': form,
            'template': template
    }.items():

        if isinstance(value, six.string_types):
            try:
                if value.startswith('http'):
                    resolved[key] = _load_from_url(value)

                elif value.startswith('/') or value.startswith('.'):
                    loaded_value = _load_from_path(value)
                    resolved[
                        key] = loaded_value if key == 'template' else json.loads(
                            loaded_value)

                elif key == 'template':
                    resolved[key] = value

                else:
                    raise PluginParamError(
                        "%s specified for command '%s' was not a "
                        "definition, file path, or URL" % (key, command_name))
            except Exception as ex:
                raise PluginParamError(
                    "Error reading %s definition from '%s' for command "
                    "'%s': %s" % (key, value, command_name, ex))

        elif value is None or (key in ['schema', 'form']
                               and isinstance(value, dict)):
            resolved[key] = value

        elif key == 'form' and isinstance(value, list):
            resolved[key] = {'type': 'fieldset', 'items': value}

        else:
            raise PluginParamError(
                "%s specified for command '%s' was not a definition, "
                "file path, or URL" % (key, command_name))

    return resolved
コード例 #3
0
def _signature_validate(cmd, method):
    # type: (Command, MethodType) -> None
    """Ensure that a Command conforms to the method signature

    This will do some validation and will raise a PluginParamError if there are any
    issues.

    It's expected that this will only be called for Parameters where this makes sense
    (aka top-level Parameters). It doesn't make sense to call this for model Parameters,
    so you shouldn't do that.

    Args:
        cmd: Command to validate
        method: Target method object

    Returns:
        None

    Raises:
        PluginParamError: There was a validation problem
    """
    for param in cmd.parameters:
        sig_param = None
        has_kwargs = False

        for p in signature(method).parameters.values():
            if p.name == param.key:
                sig_param = p
            if p.kind == InspectParameter.VAR_KEYWORD:
                has_kwargs = True

        # Couldn't find the parameter. That's OK if this parameter is meant to be part
        # of the **kwargs AND the function has a **kwargs parameter.
        if sig_param is None:
            if not param.is_kwarg:
                raise PluginParamError(
                    "Parameter was not not marked as part of kwargs and wasn't found "
                    "in the method signature (should is_kwarg be True?)")
            elif not has_kwargs:
                raise PluginParamError(
                    "Parameter was declared as a kwarg (is_kwarg=True) but the method "
                    "signature does not declare a **kwargs parameter")

        # Cool, found the parameter. Just verify that it's not pure positional and that
        # it's not marked as part of kwargs.
        else:
            if param.is_kwarg:
                raise PluginParamError(
                    "Parameter was marked as part of kwargs but was found in the "
                    "method signature (should is_kwarg be False?)")

            # I don't think this is even possible in Python < 3.8
            if sig_param.kind == InspectParameter.POSITIONAL_ONLY:
                raise PluginParamError(
                    "Sorry, positional-only type parameters are not supported")
コード例 #4
0
def parameters(*args):
    """Specify multiple Parameter definitions at once

    This can be useful for commands which have a large number of complicated
    parameters but aren't good candidates for a Model.

    .. code-block:: python

        @parameter(**params[cmd1][param1])
        @parameter(**params[cmd1][param2])
        @parameter(**params[cmd1][param3])
        def cmd1(self, **kwargs):
            pass

    Can become:

    .. code-block:: python

        @parameters(params[cmd1])
        def cmd1(self, **kwargs):
            pass

    Args:
        *args (iterable): Positional arguments
            The first (and only) positional argument must be a list containing
            dictionaries that describe parameters.

    Returns:
        func: The decorated function
    """
    if len(args) == 1:
        return functools.partial(parameters, args[0])
    elif len(args) != 2:
        raise PluginParamError("@parameters takes a single argument")

    if not isinstance(args[1], types.FunctionType):
        raise PluginParamError("@parameters must be applied to a function")

    try:
        for param in args[0]:
            parameter(args[1], **param)
    except TypeError:
        raise PluginParamError(
            "@parameters arg must be an iterable of dictionaries")

    @wrapt.decorator(enabled=_wrap_functions)
    def wrapper(_double_wrapped, _, _args, _kwargs):
        return _double_wrapped(*_args, **_kwargs)

    return wrapper(args[1])
コード例 #5
0
def _initialize_parameters(parameter_list):
    # type: (Iterable[Parameter, object, dict]) -> List[Parameter]
    """Initialize Parameters from a list of parameter definitions

    This exists for backwards compatibility with the old way of specifying Models.
    Previously, models were defined by creating a class with a ``parameters`` class
    attribute. This required constructing each parameter manually, without using the
    ``@parameter`` decorator.

    This function takes a list where members can be any of the following:
    - A Parameter object
    - A class object with a ``parameters`` attribute
    - A dict containing kwargs for constructing a Parameter

    The Parameters in the returned list will be initialized. See the function
    ``_initialize_parameter`` for information on what that entails.

    Args:
        parameter_list: List of parameter precursors

    Returns:
        List of initialized parameters
    """
    initialized_params = []

    for param in parameter_list:

        # This is already a Parameter. Only really need to interpret the choices
        # definition and recurse down into nested Parameters
        if isinstance(param, Parameter):
            initialized_params.append(_initialize_parameter(param=param))

        # This is a model class object. Needed for backwards compatibility
        # See https://github.com/beer-garden/beer-garden/issues/354
        elif hasattr(param, "parameters"):
            _deprecate(
                "Constructing a nested Parameters list using model class objects "
                "is deprecated. Please pass the model's parameter list directly."
            )
            initialized_params += _initialize_parameters(param.parameters)

        # This is a dict of Parameter kwargs
        elif isinstance(param, dict):
            initialized_params.append(_initialize_parameter(**param))

        # No clue!
        else:
            raise PluginParamError("Unable to generate parameter from '%s'" %
                                   param)

    return initialized_params
コード例 #6
0
ファイル: display.py プロジェクト: beer-garden/brewtils
def _load_from_path(path, base_dir=None):
    # type: (str, str) -> str
    """Load a definition from a path

    This is a little odd because of the differences between command-level resources and
    system-level ones and the need to be backwards-compatible.

    The problem is determining the "correct" base to use when a relative path is given.
    For command resources we're able to use ``inspect`` on the method object to
    determine the current module file. So we've always just used the directory
    containing that file as the base for relative paths.

    However, we don't have that ability when it comes to system resources. For example,
    supposed the system template is specified in an environment variable:

      BG_SYSTEM_TEMPLATE=./resources/template.html

    The only thing that really makes sense in this case is starting in the plugin's
    current working directory. In hindsight, we probably should have done that for
    command resources as well.

    Finally, having ONLY system resources start at cwd and ONLY command resources be
    dependent on the file in which they're declared would be extremely confusing.

    So in an attempt to remain compatible this will attempt to use a provided base_dir
    as the starting point for relative path resolution. If there's no file there then it
    will re-resolve the path, this time using the cwd as he starting point. If no
    base_dir is provided then it'll just use the cwd.

    Going forward this will hopefully allow us to present using the cwd as a best
    practice for everything without forcing anyone to rewrite their plugins.
    """

    if base_dir and os.path.exists(
            os.path.abspath(os.path.join(base_dir, path))):
        file_path = os.path.abspath(os.path.join(base_dir, path))
    else:
        file_path = os.path.abspath(os.path.join(os.getcwd(), path))

    try:
        with open(file_path, "r") as definition_file:
            return definition_file.read()
    except IOError as ex:
        six.raise_from(
            PluginParamError(
                "%s. Please remember that relative paths will be resolved starting "
                "from the plugin's current working directory." % ex),
            ex,
        )
コード例 #7
0
ファイル: display.py プロジェクト: beer-garden/brewtils
def resolve_template(template=None, base_dir=None):
    # type: (str, str) -> Optional[str]
    """Resolve a template attribute

    Returns:
        Dictionary that fully describes a template
    """
    if template is None:
        return None
    elif isinstance(template, six.string_types):
        try:
            if template.startswith("http"):
                return _load_from_url(template)
            elif template.startswith("/") or template.startswith("."):
                return _load_from_path(template, base_dir=base_dir)
            else:
                return template

        except Exception as ex:
            six.raise_from(
                PluginParamError("Error resolving template: %s" % ex), ex)
    else:
        raise PluginParamError(
            "Template specified was not a definition, file path, or URL")
コード例 #8
0
ファイル: display.py プロジェクト: beer-garden/brewtils
def resolve_schema(schema=None, base_dir=None):
    # type: (Union[dict, str], str) -> Optional[dict]
    """Resolve a schema attribute

    Returns:
        Dictionary that fully describes a schema
    """
    if schema is None or isinstance(schema, dict):
        return schema
    elif isinstance(schema, six.string_types):
        try:
            if schema.startswith("http"):
                return _load_from_url(schema)
            elif schema.startswith("/") or schema.startswith("."):
                return json.loads(_load_from_path(schema, base_dir=base_dir))
            else:
                raise PluginParamError(
                    "Schema was not a definition, file path, or URL")
        except Exception as ex:
            six.raise_from(PluginParamError("Error resolving schema: %s" % ex),
                           ex)
    else:
        raise PluginParamError(
            "Schema specified was not a definition, file path, or URL")
コード例 #9
0
def _parse_method(method):
    # type: (MethodType) -> Optional[Command]
    """Parse a method object as a Beer-garden command target

    If the method looks like a valid command target (based on the presence of certain
    attributes) then this method will initialize things:

    - The command will be initialized.
    - Every parameter will be initialized. Initializing a parameter is recursive - each
      nested parameter will also be initialized.
    - Top-level parameters are validated to ensure they match the method signature.

    Args:
        method: Method to parse

    Returns:
        Beergarden Command targeting the given method
    """
    if (inspect.ismethod(method) or inspect.isfunction(method)) and (hasattr(
            method, "_command") or hasattr(method, "parameters")):
        # Create a command object if there isn't one already
        method_command = _initialize_command(method)

        try:
            # Need to initialize existing parameters before attempting to add parameters
            # pulled from the method signature.
            method_command.parameters = _initialize_parameters(
                method_command.parameters + getattr(method, "parameters", []))

            # Add and update parameters based on the method signature
            _signature_parameters(method_command, method)

            # Verify that all parameters conform to the method signature
            _signature_validate(method_command, method)

        except PluginParamError as ex:
            six.raise_from(
                PluginParamError(
                    "Error initializing parameters for command '%s': %s" %
                    (method_command.name, ex)),
                ex,
            )

        return method_command
コード例 #10
0
def _initialize_command(method):
    # type: (MethodType) -> Command
    """Initialize a Command

    This takes care of ensuring a Command object is in the correct form. Things like:

    - Assigning the name from the method name
    - Pulling the description from the method docstring, if necessary
    - Resolving display modifiers (schema, form, template)

    Args:
        method: The method with the Command to initialize

    Returns:
        The initialized Command

    """
    cmd = getattr(method, "_command", Command())

    cmd.name = _method_name(method)
    cmd.description = cmd.description or _method_docstring(method)

    try:
        base_dir = os.path.dirname(inspect.getfile(method))

        cmd.schema = resolve_schema(cmd.schema, base_dir=base_dir)
        cmd.form = resolve_form(cmd.form, base_dir=base_dir)
        cmd.template = resolve_template(cmd.template, base_dir=base_dir)
    except PluginParamError as ex:
        six.raise_from(
            PluginParamError("Error initializing command '%s': %s" %
                             (cmd.name, ex)),
            ex,
        )

    return cmd
コード例 #11
0
def process_choices(choices):
    # type: (Union[dict, str, Iterable, Callable]) -> Optional[Choices]
    """Process a choices definition into a usable Choices object

    Args:
        choices: Raw choices definition, usually from a decorator

    Returns:
        Choices: Dictionary that fully describes a choices specification
    """

    if choices is None or isinstance(choices, Choices):
        return choices

    # If choices is a Callable, call it
    if callable(choices):
        choices = choices()

    if isinstance(choices, dict):
        if not choices.get("value"):
            raise PluginParamError(
                "No 'value' provided for choices. You must at least "
                "provide valid values.")

        # Again, if value is a Callable, call it
        value = choices.get("value")
        if callable(value):
            value = value()

        display = choices.get("display", _determine_display(value))
        choice_type = choices.get("type")
        strict = choices.get("strict", True)

        if choice_type is None:
            choice_type = _determine_type(value)
        elif choice_type not in Choices.TYPES:
            raise PluginParamError(
                "Invalid choices type '%s' - Valid type options are %s" %
                (choice_type, Choices.TYPES))
        else:
            if ((choice_type == "command"
                 and not isinstance(value, (six.string_types, dict)))
                    or (choice_type == "url"
                        and not isinstance(value, six.string_types))
                    or (choice_type == "static"
                        and not isinstance(value, (list, dict)))):
                allowed_types = {
                    "command": "('string', 'dictionary')",
                    "url": "('string')",
                    "static": "('list', 'dictionary)",
                }
                raise PluginParamError(
                    "Invalid choices value type '%s' - Valid value types for "
                    "choice type '%s' are %s" %
                    (type(value), choice_type, allowed_types[choice_type]))

        if display not in Choices.DISPLAYS:
            raise PluginParamError(
                "Invalid choices display '%s' - Valid display options are %s" %
                (display, Choices.DISPLAYS))

    elif isinstance(choices, str):
        value = choices
        display = _determine_display(value)
        choice_type = _determine_type(value)
        strict = True

    else:
        try:
            # Assume some sort of iterable
            value = list(choices)
        except TypeError:
            raise PluginParamError(
                "Invalid 'choices': must be a string, dictionary, or iterable."
            )

        display = _determine_display(value)
        choice_type = _determine_type(value)
        strict = True

    # Now parse out type-specific aspects
    unparsed_value = ""
    try:
        if choice_type == "command":
            if isinstance(value, six.string_types):
                unparsed_value = value
            else:
                unparsed_value = value["command"]

            details = parse(unparsed_value, parse_as="func")
        elif choice_type == "url":
            unparsed_value = value
            details = parse(unparsed_value, parse_as="url")
        else:
            if isinstance(value, dict):
                unparsed_value = choices.get("key_reference")
                if unparsed_value is None:
                    raise PluginParamError(
                        "Specifying a static choices dictionary requires a "
                        '"key_reference" field with a reference to another '
                        'parameter ("key_reference": "${param_key}")')

                details = {
                    "key_reference": parse(unparsed_value,
                                           parse_as="reference")
                }
            else:
                details = {}
    except ParseError:
        raise PluginParamError(
            "Invalid choices definition - Unable to parse '%s'" %
            unparsed_value)

    return Choices(type=choice_type,
                   display=display,
                   value=value,
                   strict=strict,
                   details=details)
コード例 #12
0
def _format_choices(choices):
    def determine_display(display_value):
        if isinstance(display_value, six.string_types):
            return 'typeahead'

        return 'select' if len(display_value) <= 50 else 'typeahead'

    def determine_type(type_value):
        if isinstance(type_value, (list, dict)):
            return 'static'
        elif type_value.startswith('http'):
            return 'url'
        else:
            return 'command'

    if not choices:
        return None

    if not isinstance(choices, (list, six.string_types, dict)):
        raise PluginParamError(
            "Invalid 'choices' provided. Must be a list, dictionary or string."
        )

    elif isinstance(choices, dict):
        if not choices.get('value'):
            raise PluginParamError(
                "No 'value' provided for choices. You must at least "
                "provide valid values.")

        value = choices.get('value')
        display = choices.get('display', determine_display(value))
        choice_type = choices.get('type')
        strict = choices.get('strict', True)

        if choice_type is None:
            choice_type = determine_type(value)
        elif choice_type not in Choices.TYPES:
            raise PluginParamError(
                "Invalid choices type '%s' - Valid type options are %s" %
                (choice_type, Choices.TYPES))
        else:
            if (choice_type == 'command' and not isinstance(value, (six.string_types, dict))) \
                    or (choice_type == 'url' and not isinstance(value, six.string_types)) \
                    or (choice_type == 'static' and not isinstance(value, (list, dict))):
                allowed_types = {
                    'command': "('string', 'dictionary')",
                    'url': "('string')",
                    'static': "('list', 'dictionary)"
                }
                raise PluginParamError(
                    "Invalid choices value type '%s' - Valid value types for "
                    "choice type '%s' are %s" %
                    (type(value), choice_type, allowed_types[choice_type]))

        if display not in Choices.DISPLAYS:
            raise PluginParamError(
                "Invalid choices display '%s' - Valid display options are %s" %
                (display, Choices.DISPLAYS))
    else:
        value = choices
        display = determine_display(value)
        choice_type = determine_type(value)
        strict = True

    # Now parse out type-specific aspects
    unparsed_value = ''
    try:
        if choice_type == 'command':
            if isinstance(value, six.string_types):
                unparsed_value = value
            else:
                unparsed_value = value['command']

            details = parse(unparsed_value, parse_as='func')
        elif choice_type == 'url':
            unparsed_value = value
            details = parse(unparsed_value, parse_as='url')
        else:
            if isinstance(value, dict):
                unparsed_value = choices.get('key_reference')
                if unparsed_value is None:
                    raise PluginParamError(
                        'Specifying a static choices dictionary requires a '
                        '"key_reference" field with a reference to another '
                        'parameter ("key_reference": "${param_key}")')

                details = {
                    'key_reference': parse(unparsed_value,
                                           parse_as='reference')
                }
            else:
                details = {}
    except ParseError:
        raise PluginParamError(
            "Invalid choices definition - Unable to parse '%s'" %
            unparsed_value)

    return Choices(type=choice_type,
                   display=display,
                   value=value,
                   strict=strict,
                   details=details)
コード例 #13
0
def parameter(_wrapped=None,
              key=None,
              type=None,
              multi=None,
              display_name=None,
              optional=None,
              default=None,
              description=None,
              choices=None,
              nullable=None,
              maximum=None,
              minimum=None,
              regex=None,
              is_kwarg=None,
              model=None,
              form_input_type=None):
    """Decorator that enables Parameter specifications for a beer-garden Command

    This decorator is intended to be used when more specification is desired for a Parameter.

    For example::

        @parameter(key="message", description="Message to echo", optional=True, type="String",
                   default="Hello, World!")
        def echo(self, message):
            return message

    :param _wrapped: The function to decorate. This is handled as a positional argument and
        shouldn't be explicitly set.
    :param key: String specifying the parameter identifier. Must match an argument name of the
        decorated function.
    :param type: String indicating the type to use for this parameter.
    :param multi: Boolean indicating if this parameter is a multi. See documentation for
        discussion of what this means.
    :param display_name: String that will be displayed as a label in the user interface.
    :param optional: Boolean indicating if this parameter must be specified.
    :param default: The value this parameter will be assigned if not overridden when creating a
        request.
    :param description: An additional string that will be displayed in the user interface.
    :param choices: List or dictionary specifying allowed values. See documentation for more
        information.
    :param nullable: Boolean indicating if this parameter is allowed to be null.
    :param maximum: Integer indicating the maximum value of the parameter.
    :param minimum: Integer indicating the minimum value of the parameter.
    :param regex: String describing a regular expression constraint on the parameter.
    :param is_kwarg: Boolean indicating if this parameter is meant to be part of the decorated
        function's kwargs.
    :param model: Class to be used as a model for this parameter. Must be a Python type object,
        not an instance.
    :param form_input_type: Only used for string fields. Changes the form input field
        (e.g. textarea)
    :return: The decorated function.
    """
    if _wrapped is None:
        return functools.partial(
            parameter,
            key=key,
            type=type,
            multi=multi,
            display_name=display_name,
            optional=optional,
            default=default,
            description=description,
            choices=choices,
            nullable=nullable,
            maximum=maximum,
            minimum=minimum,
            regex=regex,
            is_kwarg=is_kwarg,
            model=model,
            form_input_type=form_input_type,
        )

    # Create a command object if one isn't already associated
    cmd = getattr(_wrapped, '_command', None)
    if not cmd:
        cmd = _generate_command_from_function(_wrapped)
        _wrapped._command = cmd

    # Every parameter needs a key, so stop that right here
    if key is None:
        raise PluginParamError(
            "Found a parameter definition without a key for "
            "command '%s'" % cmd.name)

    # If the command doesn't already have a parameter with this key then the
    # method doesn't have an explicit keyword argument with <key> as the name.
    # That's only OK if this parameter is meant to be part of the **kwargs.
    param = cmd.get_parameter_by_key(key)
    if param is None:
        if is_kwarg:
            param = Parameter(key=key, optional=False)
            cmd.parameters.append(param)
        else:
            raise PluginParamError(
                "Parameter '%s' was not an explicit keyword argument for "
                "command '%s' and was not marked as part of kwargs (should "
                "is_kwarg be True?)" % (key, cmd.name))

    # Update parameter definition with the plugin_param arguments
    param.type = _format_type(param.type if type is None else type)
    param.multi = param.multi if multi is None else multi
    param.display_name = param.display_name if display_name is None else display_name
    param.optional = param.optional if optional is None else optional
    param.default = param.default if default is None else default
    param.description = param.description if description is None else description
    param.choices = param.choices if choices is None else choices
    param.nullable = param.nullable if nullable is None else nullable
    param.maximum = param.maximum if maximum is None else maximum
    param.minimum = param.minimum if minimum is None else minimum
    param.regex = param.regex if regex is None else regex
    param.form_input_type = param.form_input_type if form_input_type is None else form_input_type

    param.choices = _format_choices(param.choices)

    # Model is another special case - it requires its own handling
    if model is not None:
        param.type = 'Dictionary'
        param.parameters = _generate_nested_params(model)

        # If the model is not nullable and does not have a default we will try
        # to generate a one using the defaults defined on the model parameters
        if not param.nullable and not param.default:
            param.default = {}
            for nested_param in param.parameters:
                if nested_param.default:
                    param.default[nested_param.key] = nested_param.default

    @wrapt.decorator(enabled=_wrap_functions)
    def wrapper(_double_wrapped, _, _args, _kwargs):
        return _double_wrapped(*_args, **_kwargs)

    return wrapper(_wrapped)
コード例 #14
0
def _format_choices(choices):
    def determine_display(display_value):
        if isinstance(display_value, six.string_types):
            return "typeahead"

        return "select" if len(display_value) <= 50 else "typeahead"

    def determine_type(type_value):
        if isinstance(type_value, (list, dict)):
            return "static"
        elif type_value.startswith("http"):
            return "url"
        else:
            return "command"

    if not choices:
        return None

    if not isinstance(choices, (list, six.string_types, dict)):
        raise PluginParamError(
            "Invalid 'choices' provided. Must be a list, dictionary or string."
        )

    elif isinstance(choices, dict):
        if not choices.get("value"):
            raise PluginParamError(
                "No 'value' provided for choices. You must at least "
                "provide valid values.")

        value = choices.get("value")
        display = choices.get("display", determine_display(value))
        choice_type = choices.get("type")
        strict = choices.get("strict", True)

        if choice_type is None:
            choice_type = determine_type(value)
        elif choice_type not in Choices.TYPES:
            raise PluginParamError(
                "Invalid choices type '%s' - Valid type options are %s" %
                (choice_type, Choices.TYPES))
        else:
            if ((choice_type == "command"
                 and not isinstance(value, (six.string_types, dict)))
                    or (choice_type == "url"
                        and not isinstance(value, six.string_types))
                    or (choice_type == "static"
                        and not isinstance(value, (list, dict)))):
                allowed_types = {
                    "command": "('string', 'dictionary')",
                    "url": "('string')",
                    "static": "('list', 'dictionary)",
                }
                raise PluginParamError(
                    "Invalid choices value type '%s' - Valid value types for "
                    "choice type '%s' are %s" %
                    (type(value), choice_type, allowed_types[choice_type]))

        if display not in Choices.DISPLAYS:
            raise PluginParamError(
                "Invalid choices display '%s' - Valid display options are %s" %
                (display, Choices.DISPLAYS))
    else:
        value = choices
        display = determine_display(value)
        choice_type = determine_type(value)
        strict = True

    # Now parse out type-specific aspects
    unparsed_value = ""
    try:
        if choice_type == "command":
            if isinstance(value, six.string_types):
                unparsed_value = value
            else:
                unparsed_value = value["command"]

            details = parse(unparsed_value, parse_as="func")
        elif choice_type == "url":
            unparsed_value = value
            details = parse(unparsed_value, parse_as="url")
        else:
            if isinstance(value, dict):
                unparsed_value = choices.get("key_reference")
                if unparsed_value is None:
                    raise PluginParamError(
                        "Specifying a static choices dictionary requires a "
                        '"key_reference" field with a reference to another '
                        'parameter ("key_reference": "${param_key}")')

                details = {
                    "key_reference": parse(unparsed_value,
                                           parse_as="reference")
                }
            else:
                details = {}
    except ParseError:
        raise PluginParamError(
            "Invalid choices definition - Unable to parse '%s'" %
            unparsed_value)

    return Choices(type=choice_type,
                   display=display,
                   value=value,
                   strict=strict,
                   details=details)
コード例 #15
0
def _initialize_parameter(
    param=None,
    key=None,
    type=None,
    multi=None,
    display_name=None,
    optional=None,
    default=None,
    description=None,
    choices=None,
    parameters=None,
    nullable=None,
    maximum=None,
    minimum=None,
    regex=None,
    form_input_type=None,
    type_info=None,
    is_kwarg=None,
    model=None,
):
    # type: (...) -> Parameter
    """Initialize a Parameter

    This exists to move logic out of the @parameter decorator. Previously there was a
    fair amount of logic in the decorator, which meant that it wasn't feasible to create
    a Parameter without using it. This made things like nested models difficult to do
    correctly.

    There are also some checks and translation that need to happen for every Parameter,
    most notably the "choices" attribute.

    This method also ensures that these checks and translations occur for child
    Parameters.

    Args:
        param: An already-created Parameter. If this is given all the other
        Parameter-creation kwargs will be ignored

    Keyword Args:
        Will be used to construct a new Parameter
    """
    param = param or Parameter(
        key=key,
        type=type,
        multi=multi,
        display_name=display_name,
        optional=optional,
        default=default,
        description=description,
        choices=choices,
        parameters=parameters,
        nullable=nullable,
        maximum=maximum,
        minimum=minimum,
        regex=regex,
        form_input_type=form_input_type,
        type_info=type_info,
        is_kwarg=is_kwarg,
        model=model,
    )

    # Every parameter needs a key, so stop that right here
    if param.key is None:
        raise PluginParamError("Attempted to create a parameter without a key")

    # Type and type info
    # Type info is where type specific information goes. For now, this is specific
    # to file types. See #289 for more details.
    param.type = _format_type(param.type)
    param.type_info = param.type_info or {}
    if param.type in Resolvable.TYPES:
        param.type_info["storage"] = "gridfs"

        # Also nullify default parameters for safety
        param.default = None

    # Process the raw choices into a Choices object
    param.choices = process_choices(param.choices)

    # Now deal with nested parameters
    if param.parameters or param.model:
        if param.model:
            # Can't specify a model and parameters - which should win?
            if param.parameters:
                raise PluginParamError(
                    "Error initializing parameter '%s': A parameter with both a model "
                    "and nested parameters is not allowed" % param.key)

            param.parameters = param.model.parameters
            param.model = None

        param.type = "Dictionary"
        param.parameters = _initialize_parameters(param.parameters)

    return param
コード例 #16
0
def parameters(*args, **kwargs):
    """
    .. deprecated:: 3.0
        Will be removed in version 4.0. Please use ``@command`` instead.

    Decorator for specifying multiple Parameter definitions at once

    This can be useful for commands which have a large number of complicated
    parameters but aren't good candidates for a Model.

    .. code-block:: python

        @parameter(**params[cmd1][param1])
        @parameter(**params[cmd1][param2])
        @parameter(**params[cmd1][param3])
        def cmd1(self, **kwargs):
            pass

    Can become:

    .. code-block:: python

        @parameters(params[cmd1])
        def cmd1(self, **kwargs):
            pass

    Args:
        *args (iterable): Positional arguments
            The first (and only) positional argument must be a list containing
            dictionaries that describe parameters.
        **kwargs: Used for bookkeeping. Don't set any of these yourself!

    Returns:
        func: The decorated function
    """
    # This is the first invocation
    if not kwargs.get("_partial"):
        # Need the callable check to prevent applying the decorator with no parenthesis
        if len(args) == 1 and not callable(args[0]):
            return functools.partial(parameters, args[0], _partial=True)

        raise PluginParamError("@parameters takes a single argument")

    # This is the second invocation
    else:
        if len(args) != 2:
            raise PluginParamError(
                "Incorrect number of arguments for parameters partial call. Did you "
                "set _partial=True? If so, please don't do that. If not, please let "
                "the Beergarden team know how you got here!")

    _deprecate(
        "Looks like you're using the '@parameters' decorator. This is now deprecated - "
        "for passing bulk parameter definitions it's recommended to use the @command "
        "decorator parameters kwarg, like this: @command(parameters=[...])")

    params = args[0]
    _wrapped = args[1]

    if not callable(_wrapped):
        raise PluginParamError("@parameters must be applied to a callable")

    try:
        for param in params:
            parameter(_wrapped, **param)
    except TypeError:
        raise PluginParamError(
            "@parameters arg must be an iterable of dictionaries")

    return _wrapped