def travel_distance(self, value):
     if not Number.is_real_number(value):
         raise ValidationError("Travel distance must be a number (value is %s)" % value)
     elif not Number.is_finite(value):
         raise ValidationError("Travel distance must be a finite number (value is %s)" % value)
     else:
         self.set_input("travel_distance", value)
Beispiel #2
0
 def duration(self, value):
     """Sets the duration that the robot should wait for (in seconds)
     :param value: (float) Duration robot should wait
     :raises ValidationError if d is not a number
     :raises ValidationError if d is not a positive number
     """
     if isinstance(value, timedelta):
         self.set_input("duration", value)
     elif isinstance(value, basestring):
         match = TIMEDELTA_REGEX.match(value)
         if match:
             groups = match.groupdict()
             self.set_input(
                 "duration",
                 timedelta(hours=int(groups['hours']),
                           minutes=int(groups['minutes']),
                           seconds=int(groups['seconds'])))
         else:
             raise ValidationError(
                 "Duration string %s could not be parsed into a timedelta."
                 % value)
     else:
         raise ValidationError(
             "Duration must be a timedelta or string, not a %s." %
             type(value).__name__)
Beispiel #3
0
    def required_config(self, configs):
        """Sets the required hardware configurations necessary for this action.

        :param ~__builtin__.list configs: The list of required configuration names comprised of only capital letters and
        underscores.
        :raise fetchcore.exceptions.ValidationError: Thrown if configs is not a list or if any item in configs is not
        a string in the correct format.
        """
        if configs is None:
            self._set("required_config", configs)
        else:
            if not isinstance(configs, list):
                raise ValidationError("Required configurations must be a list")
            for config in configs:
                try:
                    if not re.match(re.compile(REGEX_CAPITAL_UNDERSCORE),
                                    config):
                        raise ValidationError(
                            'Required configuration "%s" is in the wrong format (should be comprised of only capital '
                            'letters and underscore)' % config)
                except TypeError:
                    raise ValidationError(
                        "All items in required configurations must be string (%s is a %s)."
                        % (config, type(config).__name__))
            self._set("required_config", configs)
Beispiel #4
0
    def modifiers(self, modifier_ids):
        """Set the the users who modified this annotation.

        :param modifier_ids: A list of user IDs.
        :type modifier_ids: list of int
        :raise: ValidationError: Thrown if modifier_ids is not a list, if each item's ID
                is not an integer, or if each item's ID in the list is not a finite positive integer.
        """
        if modifier_ids is None:
            self._set('modifiers', [])
        elif not isinstance(modifier_ids, list):
            raise ValidationError("Modifier ids must be a list, not a %s." %
                                  type(modifier_ids).__name__)
        else:
            modifier_ids = list(set(modifier_ids))
            for modifier_id in modifier_ids:
                if not Number.is_integer(modifier_id):
                    raise ValidationError(
                        "Each item in modifier IDs must be an int (%s is %s)."
                        % (modifier_id, type(modifier_id).__name__))
                elif not Number.is_finite_positive(modifier_id):
                    raise ValidationError(
                        "Each item in modifier IDs must be finite positive (item is %s)."
                        % modifier_id)
            self._set('modifiers', modifier_ids)
Beispiel #5
0
    def _get(self, field):
        """Get the given field.

        :param field: The field name to get.
        :return: The value of the field.
        :raise ValidationError: Thrown if field is not a string or an iterable.
        """

        # TODO: reconsider how the iterable field is dealt with
        if isinstance(field, basestring):
            try:
                # If field is a string, try getting the value by the field
                return self.__json[field]
            except KeyError:
                raise UndefinedFieldError("%s is not set." % field)
        elif isinstance(field, Iterable):
            if not field:
                raise ValidationError(
                    "Input attribute 'field' cannot be a empty list")
            # Get the value from the nested dictionary
            value = self.__json
            for i, f in enumerate(field):
                try:
                    value = value[f]
                except KeyError:
                    raise UndefinedFieldError("%s is not set" %
                                              ".".join(field[:i + 1]))
            return value
        else:
            raise ValidationError("Field must be a string or an iterable")
Beispiel #6
0
    def planning_robot_radius(self, planning_robot_radius):
        """Set the robot's estimated radius for planning.

        :param float planning_robot_radius: The robot radius for planning.
        :raise: fetchcore.exceptions.ValidationError: Thrown if planning_robot_radius is:

          - not a finite non-negative number.
          - None while the planning footprint type is 'radius'.
        """
        if planning_robot_radius is None:
            if self.planning_footprint_type == FootprintTypes.RADIUS:
                raise ValidationError(
                    "Planning robot radius cannot be set to None if planning footprint type is %s."
                    % FootprintTypes.RADIUS)
            self._set('planning_robot_radius', planning_robot_radius)
        elif Number.is_real_number(planning_robot_radius):
            if not Number.is_finite_non_negative(planning_robot_radius):
                raise ValidationError(
                    "Planning robot radius must be a finite non-negative number (was given as %s)."
                    % planning_robot_radius)
            self._set('planning_robot_radius', planning_robot_radius)
        else:
            raise ValidationError(
                "Planning Robot radius must be a real number (was given as %s)."
                % planning_robot_radius)
Beispiel #7
0
    def __set_footprint_blacklist(self, new_blacklist, blacklist_type,
                                  footprint_class):
        """Set the value of robot/cart footprint blacklist.

        :param new_blacklist: (iterable) New value to set to the blacklist.
        :param blacklist_type: (string) Type of the blacklist as a string. Should be either robot_footprint or
            cart_footprint.
        :param footprint_class: Resource class of this blacklist type.
        """
        if blacklist_type not in ('robot_footprint', 'cart_footprint'):
            raise ValidationError(
                'EdgeDraft.__set_footprint_blacklist received an unrecognized blacklist type.'
            )
        json_field = blacklist_type + '_blacklist'
        readable_blacklist_type = ' '.join(blacklist_type.split('_')).title()
        if new_blacklist is None:
            self._set(json_field, [])
        elif isinstance(new_blacklist,
                        collections.Iterable) and not isinstance(
                            new_blacklist, basestring):
            old_blacklist = deepcopy(
                self._get(json_field)) if self.is_set(json_field) else []
            self._set(json_field, [])
            for footprint in new_blacklist:
                try:
                    self.__add_footprint_to_blacklist(footprint,
                                                      blacklist_type,
                                                      footprint_class)
                except ValidationError as e:
                    self._set(json_field, old_blacklist)
                    raise e
        else:
            raise ValidationError(
                '%s blacklist must be an iterable or None, not a %s.' %
                (readable_blacklist_type, type(new_blacklist).__name__))
Beispiel #8
0
    def task_templates(self, task_templates):
        """Set the associated task templates for this schedule.

        :param task_templates: (list of integers|list of TaskTemplates) A list of task templates or task template IDs.
        :raise: fetchcore.exceptions.ValidationError Thrown if task_templates is not a list, if each item in values
                is not an integer, or if each item in the list is not a finite positive integer.
        """
        from .task_template import TaskTemplate
        if task_templates is None:
            self.task_template_ids = task_templates
        elif not isinstance(task_templates, (list, tuple)):
            raise ValidationError(
                'Task templates must be either None, a list or a tuple.')
        elif not all(
                isinstance(task_template, (int, TaskTemplate))
                for task_template in task_templates):
            raise ValidationError(
                'Task templates must be a list of TaskTemplate objects or integers.'
            )
        else:
            for task_template in task_templates:
                if isinstance(task_template, TaskTemplate) and not hasattr(
                        task_template, 'id'):
                    task_template.save()
            self.task_template_ids = [
                task_template.id
                if isinstance(task_template, TaskTemplate) else task_template
                for task_template in task_templates
            ]
Beispiel #9
0
    def __init__(
            self, id=None, action=None, survey_pose_states=None, survey_node_states=None, created=None, modified=None,
            **kwargs):
        """
        :param id: The ID of the survey state.
        :param action: The action that this state belongs to.
        :param survey_pose_states: The survey poses associated with this survey state.
        :param survey_node_states: The survey nodes associated with this survey state.
        :param created: The date and time of this survey state's creation.
        :param modified: The date and time this survey state was last modified.
        :type created: str, ~datetime.datetime
        :type modified: str, ~datetime.datetime
        """
        super(SurveyState, self).__init__(id=id, created=created, modified=modified, **kwargs)

        self.action = action
        if survey_pose_states is None:
            self._set('survey_pose_states', [])
        elif not isinstance(survey_pose_states, list):
            raise ValidationError("Survey poses must be a list, not a %s." % type(survey_pose_states).__name__)
        else:
            self._set('survey_pose_states', survey_pose_states)
        if survey_node_states is None:
            self._set('survey_node_states', [])
        elif not isinstance(survey_node_states, list):
            raise ValidationError("Survey nodes must be a list, not a %s." % type(survey_node_states).__name__)
        else:
            self._set('survey_node_states', survey_node_states)
Beispiel #10
0
    def __remove_footprint_from_blacklist(self, footprint, blacklist_type,
                                          footprint_class):
        """Remove a footprint from a blacklist.

        :param footprint: Footprint to remove from a blacklist.
        :param blacklist_type: (string) Type of the blacklist as a string. Should be either robot_footprint or
            cart_footprint.
        :param footprint_class: Resource class of this blacklist type.
        """
        if blacklist_type not in ('robot_footprint', 'cart_footprint'):
            raise ValidationError(
                'EdgeDraft.__remove_footprint_from_blacklist received an unrecognized blacklist type.'
            )
        if type(footprint) != footprint_class and not isinstance(
                footprint, basestring):
            readable_blacklist_type = ' '.join(
                blacklist_type.split('_')).title()
            raise ValidationError(
                '%s must be a string or a %s object (%s is a %s).' %
                (readable_blacklist_type, footprint_class.__name__,
                 str(footprint), type(footprint).__name__))
        json_field = blacklist_type + '_blacklist'
        if self.is_set(json_field):
            try:
                self._get(json_field).remove(footprint if isinstance(
                    footprint, basestring) else footprint.name)
            except ValueError:
                # Footprint is not in list
                pass
Beispiel #11
0
    def task_template_ids(self, task_template_ids):
        """Set the associated task templates IDs for this schedule.

        :param task_template_ids: A list of task template IDs.
        :type task_template_ids: list of int
        :raise: ValidationError: Thrown if task_template_ids is not a list, if each item's ID
                is not an integer, or if each item's ID in the list is not a finite positive integer.
        """
        if task_template_ids is None:
            self._set('task_templates', [])
        elif not isinstance(task_template_ids, list):
            raise ValidationError(
                "Task template ids must be a list, not a %s." %
                type(task_template_ids).__name__)
        else:
            task_template_ids = list(set(task_template_ids))
            for task_template_id in task_template_ids:
                if not Number.is_integer(task_template_id):
                    raise ValidationError(
                        "Each item in task template IDs must be an int (%s is %s)."
                        % (task_template_id, type(task_template_id).__name__))
                elif not Number.is_finite_positive(task_template_id):
                    raise ValidationError(
                        "Each item in task template IDs must be finite positive (item is %s)."
                        % task_template_id)
            self._set('task_templates', task_template_ids)
Beispiel #12
0
    def __init__(self, id=None, version=None, parent=None, map=None, areas=None, name=None, created=None,
                 modified=None, **kwargs):
        """
        :param integer id: The ID of the revision (assigned automatically upon creation).
        :param integer version: The version of this revision. If NULL, it is considered to be a draft,
            unpublished version.
        :param parent: The parent revision that this revision was based off of.
        :param integer map: The map this revision is associated with.
        :param string name: The display name for this revision.
        :param created: The date and time of this revision's creation (assigned automatically).
        :param modified: The date and time this revision was last modified (updated automatically).
        :type parent: int, Revision
        :type created: str, ~datetime.datetime
        :type modified: str, ~datetime.datetime
        """
        super(Revision, self).__init__(id=id, created=created, modified=modified, **kwargs)

        self.version = version
        self.parent = parent
        self.map = map
        self.name = name

        if Number.is_integer(map):
            if not Number.is_finite_positive(map):
                raise ValidationError("Map ID must be finite positive (item is %s)." % map)
            self.endpoint = 'maps/%(map_id)s/revisions' % {'map_id': str(map)}
        else:
            raise ValidationError("Map ID must be an integer (%s is %s)." % (map, type(map).__name__))

        if areas:
            self.__areas = areas
    def available_action_names(self, available_actions):
        """Sets the available action names for this configuration.

        :param available_actions: (list) The list of available action names comprised of only capital letters and
        underscores.
        :raise fetchcore.exceptions.ValidationError: Thrown if available_actions is not a list or if any item in
        available_actions is not a string in the correct format
        """
        if available_actions is None:
            self._set("available_actions", [])
        elif not isinstance(available_actions, list):
            raise ValidationError("Available actions must be a list")
        else:
            for available_action in available_actions:
                try:
                    if not re.match(CAPITAL_UNDERSCORE_PATTERN,
                                    available_action):
                        raise ValidationError(
                            "Available action %s does not match the pattern %s"
                            % (available_action, REGEX_CAPITAL_UNDERSCORE))
                except TypeError:
                    raise ValidationError(
                        "All items in available actions must be string (%s is a %s)."
                        % (available_action, type(available_action).__name__))
            self._set("available_actions", available_actions)
Beispiel #14
0
    def clearing_footprint_type(self, clearing_footprint_type):
        """Set the type of clearing footprint described for this configuration.

        :param str clearing_footprint_type: The footprint type for clearing.
        :raise fetchcore.exceptions.ValidationError: Thrown if value is:

          - not a string
        """
        if isinstance(clearing_footprint_type, basestring):
            if not clearing_footprint_type:
                raise ValidationError("Footprint type cannot be empty.")
            if clearing_footprint_type == FootprintTypes.RADIUS:
                try:
                    if self.clearing_robot_radius is None or self.clearing_inflation_factor is None:
                        raise ValidationError(
                            "Cannot set clearing footprint type to %s when either clearing robot "
                            "radius or clearing inflation factor is None." %
                            FootprintTypes.RADIUS)
                except UndefinedFieldError:
                    raise ValidationError(
                        "Cannot set clearing footprint type to %s when either clearing robot radius "
                        "or clearing inflation factor is not set." %
                        FootprintTypes.RADIUS)
            self._set('clearing_footprint_type', clearing_footprint_type)
        else:
            raise ValidationError(
                "Clearing footprint type must be a string, not a %s." %
                type(clearing_footprint_type).__name__)
Beispiel #15
0
    def clearing_polygon(self, clearing_polygon):
        """Set the clearing polygon for this configuration.

        :param clearing_polygon: The polygon for clearing.
        :type: A list of three or more dicts, each containing number values for keys 'x' and 'y'
        """
        if clearing_polygon is None:
            self._set('clearing_polygon', clearing_polygon)
        elif isinstance(clearing_polygon, (list, tuple)):
            if len(clearing_polygon) < 3:
                raise ValidationError(
                    "Length of clearing polygon entry must be greater than or equal to 3 (given "
                    "length was %s)." % len(clearing_polygon))
            for item in clearing_polygon:
                if not isinstance(item, dict):
                    raise ValidationError(
                        "Provided point '%s' for clearing polygon entry is a %s instead of a dict."
                        % (item, type(item).__name__))
                if len(item) == 2 and all(key in item for key in ('x', 'y')) and \
                        all(Number.is_finite(num) and Number.is_real_number(num) for num in item.values()):
                    continue
                else:
                    raise ValidationError(
                        "Provided point %s for clearing polygon is not a length 2 dictionary "
                        "containing real, finite numbers for 'x' and 'y'." %
                        item)
            self._set('clearing_polygon', clearing_polygon)
        else:
            raise ValidationError(
                "Clearing polygon must be a list, tuple, or None, not a %s." %
                type(clearing_polygon).__name__)
Beispiel #16
0
    def planning_inflation_factor(self, planning_inflation_factor):
        """Set the factor by which to inflate the robot's radius for planning.

        :param float planning_inflation_factor: The inflation factor for planning.
        :raise: fetchcore.exceptions.ValidationError: Thrown if planning_inflation_factor is:

          - not a finite non-negative number.
          - None while the planning footprint type is 'radius'.
        """
        if planning_inflation_factor is None:
            if self.planning_footprint_type == FootprintTypes.RADIUS:
                raise ValidationError(
                    "Planning robot radius cannot be set to None if planning footprint type is %s."
                    % FootprintTypes.RADIUS)
            self._set('planning_inflation_factor', planning_inflation_factor)
        elif Number.is_real_number(planning_inflation_factor):
            if not Number.is_finite_non_negative(planning_inflation_factor):
                raise ValidationError(
                    "Planning inflation factor must be a finite positive number (was given as %s)."
                    % planning_inflation_factor)
            self._set('planning_inflation_factor', planning_inflation_factor)
        else:
            raise ValidationError(
                "Planning inflation factor must be a real number (was given as %s)."
                % planning_inflation_factor)
Beispiel #17
0
    def __add_footprint_to_blacklist(self, footprint, blacklist_type,
                                     footprint_class):
        """Add a footprint to a blacklist.

        :param footprint: New footprint to add to a blacklist.
        :param blacklist_type: (string) Type of the blacklist as a string. Should be either robot_footprint or
            cart_footprint.
        :param footprint_class: Resource class of this blacklist type.
        """
        if blacklist_type not in ('robot_footprint', 'cart_footprint'):
            raise ValidationError(
                'EdgeDraft.__add_footprint_to_blacklist received an unrecognized blacklist type.'
            )
        if type(footprint) != footprint_class and not isinstance(
                footprint, basestring):
            readable_blacklist_type = ' '.join(
                blacklist_type.split('_')).title()
            raise ValidationError(
                '%s must be a string or a %s object (%s is a %s).' %
                (readable_blacklist_type, footprint_class.__name__,
                 str(footprint), type(footprint).__name__))
        json_field = blacklist_type + '_blacklist'
        if not self.is_set(json_field):
            self._set(json_field, [])
        blacklist = self._get(json_field)
        new_footprint = footprint if isinstance(footprint,
                                                basestring) else footprint.name
        if new_footprint not in blacklist:
            blacklist.append(new_footprint)
        self._set(json_field, blacklist)
    def assignable_robot_names(self, assignable_robot_names):
        """Set the list of robot names assigned to this task template.

        :param assignable_robot_names: A list of assignable robot names.
        :type assignable_robot_names: list of int
        :raise: fetchcore.exceptions.ValidationError Thrown if assignable_robot_names is not a list or if each item's
                name is not None or not a Freight's name.
        """
        if assignable_robot_names is None:
            self._set('assignable_robots', [])
        elif not isinstance(assignable_robot_names, list):
            raise ValidationError(
                "Assignable robot names must be a list, not a %s." %
                type(assignable_robot_names).__name__)
        else:
            for assignable_robot_name in assignable_robot_names:
                try:
                    if not re.compile(ROBOT_NAME_PATTERN).match(
                            assignable_robot_name):
                        raise ValidationError(
                            'Only Freights can be assigned to tasks. %s does not seem to be a Freight.'
                            % assignable_robot_name)
                except TypeError:
                    raise ValidationError(
                        'Robot name can only be the name of a Freight or None.'
                    )
            self._set("assignable_robots", assignable_robot_names)
    def assignable_robots(self, assignable_robots):
        """Set the list of robots assigned to this task template.

        :param assignable_robots: A list of assignable robots or assignable robot
            names.
        :type assignable_robots: list of integers, list of Robots
        :raise: fetchcore.exceptions.ValidationError Thrown if assignable_robots is not a list, if each item in values
            is not a AssignableRobot object, or if each item in the list is not a finite positive integer.
        """
        if assignable_robots is None:
            self.assignable_robot_names = assignable_robots
        elif not isinstance(assignable_robots, (list, tuple)):
            raise ValidationError(
                'Assignable robots must be either None, a list or a tuple.')
        elif not all(
                isinstance(assignable_robot, (basestring, Robot))
                for assignable_robot in assignable_robots):
            raise ValidationError(
                'Assignable robots must be a list of Robot objects or strings.'
            )
        else:
            self.assignable_robot_names = [
                assignable_robot.name
                if isinstance(assignable_robot, Robot) else assignable_robot
                for assignable_robot in assignable_robots
            ]
 def cart_footprint_name(self, value):
     try:
         if re.match(REGEX_CAPITAL_UNDERSCORE, value):
             self.set_input("cart_footprint_name", value)
         else:
             raise ValidationError("%s does not match the pattern %s" % (value, REGEX_CAPITAL_UNDERSCORE))
     except TypeError:
         raise ValidationError("Cart footprint name must be a string, not a %s" % type(value).__name__)
Beispiel #21
0
 def shape_name(self, value):
     if not isinstance(value, basestring):
         raise ValidationError("Shape name must be a string, not a %s" %
                               type(value).__name__)
     elif value not in ComponentShapes:
         raise ValidationError(
             "Shape name '%s' is not a valid component shape." % value)
     else:
         self._set('shape_name', value)
 def goal_yaw_tolerance(self, value):
     if not Number.is_real_number(value):
         raise ValidationError(
             "Goal yaw tolerance must be a number (value is %s)" % value)
     elif not Number.is_finite(value):
         raise ValidationError(
             "Goal yaw tolerance must be a finite number (value is %s)" %
             value)
     else:
         self.set_input("goal_yaw_tolerance", value)
    def sound_id(self, sound_id):
        """Sets the ID of the sound file to be played

        :param sound_id: (string) The sound_id
        :raises ValidationError if sound_id is not an integer or if sound_id is None
        """
        if sound_id is None:
            raise ValidationError("Sound ID must not be none")
        elif not isinstance(sound_id, int):
            raise ValidationError("Sound ID must be an integer.")
        self.set_input("sound_id", sound_id)
Beispiel #24
0
 def dict_to_float_list(value):
     if isinstance(value, dict):
         try:
             return [value[key] for key in ['x', 'y', 'theta']]
         except ValueError:
             raise ValidationError(
                 "%s is not a pose dict (a dict containing 3 finite numbers assigned to x, y,"
                 " and theta)" % value)
     else:
         raise ValidationError("Value must be a dict, not %s" %
                               type(value).__name__)
 def hint_position_tolerance(self, value):
     if not Number.is_real_number(value):
         raise ValidationError(
             "Hint position tolerance must be a number (value is %s)" %
             value)
     elif not Number.is_finite(value):
         raise ValidationError(
             "Hint position tolerance must be a finite number (value is %s)"
             % value)
     else:
         self.set_input("hint_position_tolerance", value)
Beispiel #26
0
    def survey_path_id(self, value):
        """Sets the ID of survey path for this action to follow.

        :param value: (integer) Survey path ID
        :raises ValidationError if value is not a positive finite integer
        """
        if Number.is_integer(value):
            if not Number.is_finite_positive(value):
                raise ValidationError("Survey path ID must be a finite positive number (value is %s)" % value)
            self.set_input("survey_path_id", value)
        else:
            raise ValidationError("Survey path ID must be a number (value is %s)" % value)
Beispiel #27
0
    def password(self, value):
        """Set the robot's password.

        :param password: (string) The robot's password.
        :raise fetchcore.exceptions.ValidationError: Thrown if password is not a string or if password is empty.
        """
        if isinstance(value, basestring):
            if not value:
                raise ValidationError("Password cannot be empty.")
            self._set('password', value)
        else:
            raise ValidationError("Password must be a string, not a %s." % type(value).__name__)
Beispiel #28
0
    def ip(self, ip):
        """Set the IP address of the reporter of this log.

        :param str ip: The IP address of the reporter of this log.
        :raise: fetchcore.exceptions.ValidationError Thrown if the ip is not a valid IP address string.
        """
        if not IP.valid_type(ip):
            raise ValidationError("IP must be a string.")
        elif not IP.valid_address(ip):
            raise ValidationError("%s is not a legal IP address." % ip)
        else:
            self._set('ip', ip)
Beispiel #29
0
    def map(self, map):
        """Set the map that this revision belongs to.

        :param integer map: The map.
        :raise fetchcore.exceptions.ValidationError:: Thrown if map not a finite positive integer.
        """
        if Number.is_integer(map):
            if not Number.is_finite_positive(map):
                raise ValidationError("Map ID must be finite positive (item is %s)." % map)
            self._set('map', map)
        else:
            raise ValidationError("Map ID must be an integer (%s is %s)." % (map, type(map).__name__))
Beispiel #30
0
    def distance_ratio(self, distance_ratio):
        """Sets the distance along the edge where this pose sits (bound between 0 and 1).

        :param float distance_ratio: The distance ratio.
        :raise fetchcore.exceptions.ValidationError: Thrown if distance_ratio is not a finite number in between 0 and 1.
        """
        if not Number.is_real_number(distance_ratio):
            raise ValidationError("Distance ratio must be a number (distance_ratio is %s)" % distance_ratio)
        elif distance_ratio < 0 or distance_ratio > 1:
            raise ValidationError("Distance ratio must be between 0 and 1 (distance_ratio is %s)" % distance_ratio)
        else:
            self._set("distance_ratio", distance_ratio)