Beispiel #1
0
def within_distance(node, context):
    confirm_args_count(node, 4)
    property_name, coords, distance, unit = node.args
    property_name = _property_name_to_string(property_name, node)

    try:
        geo_point = GeoPoint.from_string(coords, flexible=True)
    except BadValueError as e:
        raise XPathFunctionException(
            _(f"The second argument to '{node.name}' must be valid coordinates"
              ), serialize(node)) from e

    try:
        distance = float(distance)
    except ValueError as e:
        raise XPathFunctionException(
            _(f"The third argument to '{node.name}' must be a number, got '{distance}'"
              ), serialize(node)) from e

    if unit not in DISTANCE_UNITS:
        raise XPathFunctionException(
            _(f"'{unit}' is not a valid distance unit. Expected one of {', '.join(DISTANCE_UNITS)}"
              ), serialize(node))

    return case_property_geo_distance(property_name, geo_point,
                                      **{unit: distance})
Beispiel #2
0
    def visit(node):

        if isinstance(node, FunctionCall):
            if node.name in XPATH_QUERY_FUNCTIONS:
                return XPATH_QUERY_FUNCTIONS[node.name](node, context)
            else:
                raise XPathFunctionException(
                    _("'{name}' is not a valid standalone function").format(
                        name=node.name), serialize(node))

        if not hasattr(node, 'op'):
            raise CaseFilterError(
                _("Your search query is required to have at least one boolean operator ({boolean_ops})"
                  ).format(boolean_ops=", ".join(COMPARISON_OPERATORS), ),
                serialize(node))

        if _is_ancestor_case_lookup(node):
            # this node represents a filter on a property for a related case
            return _walk_ancestor_cases(node)

        if _is_subcase_count(node):
            return XPATH_QUERY_FUNCTIONS['subcase-count'](node, context)

        if node.op in COMPARISON_OPERATORS:
            # This node is a leaf
            return _comparison(node)

        if node.op in list(OPERATOR_MAPPING.keys()):
            # This is another branch in the tree
            return OPERATOR_MAPPING[node.op](visit(node.left),
                                             visit(node.right))

        raise CaseFilterError(
            _("We don't know what to do with operator '{}'. Please try reformatting your query."
              .format(node.op)), serialize(node))
Beispiel #3
0
def _property_name_to_string(value, node):
    if isinstance(value, Step):
        return serialize(value)
    if isinstance(value, str):
        return value
    raise XPathFunctionException(
        _(f"The first argument to '{node.name}' must be a valid case property name"
          ), serialize(node))
Beispiel #4
0
def date_add(node, context):
    from corehq.apps.case_search.dsl_utils import unwrap_value

    assert node.name == 'date-add'

    confirm_args_count(node, 3)

    date_arg = unwrap_value(node.args[0], context)
    date_value = _value_to_date(node, date_arg)

    interval_type = unwrap_value(node.args[1], context)
    interval_types = ("days", "weeks", "months", "years")
    if interval_type not in interval_types:
        raise XPathFunctionException(
            _("The \"date-add\" function expects the 'interval' argument to be one of {types}"
              ).format(types=interval_types), serialize(node))

    quantity = unwrap_value(node.args[2], context)
    if isinstance(quantity, str):
        try:
            quantity = float(quantity)
        except (ValueError, TypeError):
            raise XPathFunctionException(
                _("The \"date-add\" function expects the interval quantity to be a numeric value"
                  ), serialize(node))

    if not isinstance(quantity, (int, float)):
        raise XPathFunctionException(
            _("The \"date-add\" function expects the interval quantity to be a numeric value"
              ), serialize(node))

    if interval_type in ("years", "months") and int(quantity) != quantity:
        raise XPathFunctionException(
            _("Non-integer years and months are ambiguous and not supported by the \"date-add\" function"
              ), serialize(node))

    try:
        result = date_value + relativedelta(**{interval_type: quantity})
    except Exception as e:
        # catchall in case of an unexpected error
        raise XPathFunctionException(str(e), serialize(node))

    return result.strftime(ISO_DATE_FORMAT)
Beispiel #5
0
def _value_to_date(node, value):
    if isinstance(value, int):
        parsed_date = datetime.date(1970, 1,
                                    1) + datetime.timedelta(days=value)
    elif isinstance(value, str):
        try:
            parsed_date = parse_date(value)
        except ValueError:
            raise XPathFunctionException(
                _("{} is not a valid date").format(value), serialize(node))
    elif isinstance(value, datetime.date):
        parsed_date = value
    else:
        parsed_date = None

    if parsed_date is None:
        raise XPathFunctionException(
            _("Invalid date value. Dates must be an integer or a string of the format \"YYYY-mm-dd\""
              ), serialize(node))

    return parsed_date
Beispiel #6
0
def confirm_args_count(node, expected):
    actual = len(node.args)
    if actual != expected:
        raise XPathFunctionException(
            _(f"The '{node.name}' function accepts exactly {expected} arguments, got {actual}"
              ), serialize(node))
Beispiel #7
0
def _extract_subcase_query_parts(node):
    current_node = node
    if isinstance(node, BinaryExpression):
        count_op = node.op
        case_count = node.right
        current_node = node.left

        if count_op not in [">", "<", "<=", ">=", "=", "!="]:
            raise XPathFunctionException(
                _("Unsupported operator for use with 'subcase-count': {op}").format(op=count_op),
                serialize(node)
            )

        if isinstance(case_count, UnaryExpression):
            if case_count.op == '+':
                case_count = case_count.right
            else:
                raise XPathFunctionException(
                    _("'subcase-count' must be compared to a positive integer"),
                    serialize(node)
                )

        try:
            case_count = int(case_count)
        except (ValueError, TypeError):
            raise XPathFunctionException(
                _("'subcase-count' must be compared to a positive integer"),
                serialize(node)
            )

        if not isinstance(current_node, FunctionCall) or str(current_node.name) != "subcase-count":
            raise XPathFunctionException(
                _("XPath incorrectly formatted. Expected 'subcase-count'"),
                serialize(current_node)
            )

    else:
        if not isinstance(node, FunctionCall) or str(node.name) != "subcase-exists":
            raise XPathFunctionException(
                _("XPath incorrectly formatted. Expected 'subcase-exists'"),
                serialize(node)
            )

        case_count = 0
        count_op = ">"

    args = current_node.args
    if not 1 <= len(args) <= 2:
        raise XPathFunctionException(
            _("'{name}' expects one or two arguments").format(name=current_node.name),
            serialize(node)
        )
    index_identifier = args[0]
    subcase_filter = args[1] if len(args) == 2 else None

    if not isinstance(index_identifier, str):
        raise XPathFunctionException(
            _("'{name}' error. Index identifier must be a string").format(name=current_node.name),
            serialize(node)
        )

    return index_identifier, subcase_filter, count_op, case_count