Пример #1
0
    def parse_query_string(self, query_string):
        # pylint: disable=too-many-locals
        """
        Function that parse the querystring, extracting infos for limit, offset,
        ordering, filters, attribute and extra projections.
        :param query_string (as obtained from request.query_string)
        :return: parsed values for the querykeys
        """

        from pyparsing import Word, alphas, nums, alphanums, printables, \
            ZeroOrMore, OneOrMore, Suppress, Optional, Literal, Group, \
            QuotedString, Combine, \
            StringStart as SS, StringEnd as SE, \
            WordEnd as WE, \
            ParseException

        from pyparsing import pyparsing_common as ppc
        from dateutil import parser as dtparser
        from psycopg2.tz import FixedOffsetTimezone

        ## Define grammar
        # key types
        key = Word(f'{alphas}_', f'{alphanums}_')
        # operators
        operator = (Literal('=like=') | Literal('=ilike=') | Literal('=in=')
                    | Literal('=notin=') | Literal('=') | Literal('!=')
                    | Literal('>=') | Literal('>') | Literal('<=')
                    | Literal('<'))
        # Value types
        value_num = ppc.number
        value_bool = (
            Literal('true')
            | Literal('false')).addParseAction(lambda toks: bool(toks[0]))
        value_string = QuotedString('"', escQuote='""')
        value_orderby = Combine(Optional(Word('+-', exact=1)) + key)

        ## DateTimeShift value. First, compose the atomic values and then
        # combine
        #  them and convert them to datetime objects
        # Date
        value_date = Combine(
            Word(nums, exact=4) + Literal('-') + Word(nums, exact=2) +
            Literal('-') + Word(nums, exact=2))
        # Time
        value_time = Combine(
            Literal('T') + Word(nums, exact=2) +
            Optional(Literal(':') + Word(nums, exact=2)) +
            Optional(Literal(':') + Word(nums, exact=2)))
        # Shift
        value_shift = Combine(
            Word('+-', exact=1) + Word(nums, exact=2) +
            Optional(Literal(':') + Word(nums, exact=2)))
        # Combine atomic values
        value_datetime = Combine(
            value_date + Optional(value_time) + Optional(value_shift) +
            WE(printables.replace('&', ''))
            # To us the
            # word must end with '&' or end of the string
            # Adding  WordEnd  only here is very important. This makes atomic
            # values for date, time and shift not really
            # usable alone individually.
        )

        ########################################################################

        def validate_time(toks):
            """
            Function to convert datetime string into datetime object. The format is
            compliant with ParseAction requirements

            :param toks: datetime string passed in tokens
            :return: datetime object
            """

            datetime_string = toks[0]

            # Check the precision
            precision = len(datetime_string.replace('T', ':').split(':'))

            # Parse
            try:
                dtobj = dtparser.parse(datetime_string)
            except ValueError:
                raise RestInputValidationError(
                    'time value has wrong format. The '
                    'right format is '
                    '<date>T<time><offset>, '
                    'where <date> is expressed as '
                    '[YYYY]-[MM]-[DD], '
                    '<time> is expressed as [HH]:[MM]:['
                    'SS], '
                    '<offset> is expressed as +/-[HH]:['
                    'MM] '
                    'given with '
                    'respect to UTC')
            if dtobj.tzinfo is not None and dtobj.utcoffset() is not None:
                tzoffset_minutes = int(dtobj.utcoffset().total_seconds() // 60)
                return DatetimePrecision(
                    dtobj.replace(tzinfo=FixedOffsetTimezone(
                        offset=tzoffset_minutes, name=None)), precision)

            return DatetimePrecision(
                dtobj.replace(tzinfo=FixedOffsetTimezone(offset=0, name=None)),
                precision)

        ########################################################################

        # Convert datetime value to datetime object
        value_datetime.setParseAction(validate_time)

        # More General types
        value = (value_string | value_bool | value_datetime | value_num
                 | value_orderby)
        # List of values (I do not check the homogeneity of the types of values,
        # query builder will do it somehow)
        value_list = Group(value + OneOrMore(Suppress(',') + value) +
                           Optional(Suppress(',')))

        # Fields
        single_field = Group(key + operator + value)
        list_field = Group(key + (Literal('=in=') | Literal('=notin=')) +
                           value_list)
        orderby_field = Group(key + Literal('=') + value_list)
        field = (list_field | orderby_field | single_field)

        # Fields separator
        separator = Suppress(Literal('&'))

        # General query string
        general_grammar = SS() + Optional(field) + ZeroOrMore(
            separator + field) + \
                          Optional(separator) + SE()

        ## Parse the query string
        try:
            fields = general_grammar.parseString(query_string)

            # JQuery adds _=timestamp a parameter to not use cached data/response.
            # To handle query, remove this "_" parameter from the query string
            # For more details check issue #789
            # (https://github.com/aiidateam/aiida-core/issues/789) in aiida-core
            field_list = [
                entry for entry in fields.asList() if entry[0] != '_'
            ]

        except ParseException as err:
            raise RestInputValidationError(
                'The query string format is invalid. '
                "Parser returned this massage: \"{"
                "}.\" Please notice that the column "
                'number '
                'is counted from '
                'the first character of the query '
                'string.'.format(err))

        ## return the translator instructions elaborated from the field_list
        return self.build_translator_parameters(field_list)
Пример #2
0
def parse_query_string(query_string):
    """
    Function that parse the querystring, extracting infos for limit, offset,
    ordering, filters, attribute and extra projections.
    :param query_string (as obtained from request.query_string)
    :return: parsed values for the querykeys
    """

    from pyparsing import Word, alphas, nums, alphanums, printables, \
        ZeroOrMore, OneOrMore, Suppress, Optional, Literal, Group, \
        QuotedString, Combine, \
        StringStart as SS, StringEnd as SE, \
        WordStart as WS, WordEnd as WE, \
        ParseException

    from pyparsing import pyparsing_common as ppc
    from dateutil import parser as dtparser
    from psycopg2.tz import FixedOffsetTimezone

    ## Define grammar
    # key types
    key = Word(alphas + '_', alphanums + '_')
    # operators
    operator = (Literal('=like=') | Literal('=ilike=') | Literal('=in=')
                | Literal('=notin=') | Literal('=') | Literal('!=')
                | Literal('>=') | Literal('>') | Literal('<=') | Literal('<'))
    # Value types
    valueNum = ppc.number
    valueBool = (Literal('true')
                 | Literal('false')).addParseAction(lambda toks: bool(toks[0]))
    valueString = QuotedString('"', escQuote='""')
    valueOrderby = Combine(Optional(Word('+-', exact=1)) + key)

    ## DateTimeShift value. First, compose the atomic values and then combine
    #  them and convert them to datetime objects
    # Date
    valueDate = Combine(
        Word(nums, exact=4) + Literal('-') + Word(nums, exact=2) +
        Literal('-') + Word(nums, exact=2))
    # Time
    valueTime = Combine(
        Literal('T') + Word(nums, exact=2) +
        Optional(Literal(':') + Word(nums, exact=2)) +
        Optional(Literal(':') + Word(nums, exact=2)))
    # Shift
    valueShift = Combine(
        Word('+-', exact=1) + Word(nums, exact=2) +
        Optional(Literal(':') + Word(nums, exact=2)))
    # Combine atomic values
    valueDateTime = Combine(
        valueDate + Optional(valueTime) + Optional(valueShift) +
        WE(printables.translate(None, '&'))  # To us the
        # word must end with '&' or end of the string
        # Adding  WordEnd  only here is very important. This makes atomic
        # values for date, time and shift not really
        # usable alone individually.
    )

    ############################################################################
    # Function to convert datetime string into datetime object. The format is
    # compliant with ParseAction requirements
    def validate_time(s, loc, toks):

        datetime_string = toks[0]

        # Check the precision
        precision = len(datetime_string.replace('T', ':').split(':'))

        # Parse
        try:
            dt = dtparser.parse(datetime_string)
        except ValueError:
            raise RestInputValidationError("time value has wrong format. The "
                                           "right format is "
                                           "<date>T<time><offset>, "
                                           "where <date> is expressed as "
                                           "[YYYY]-[MM]-[DD], "
                                           "<time> is expressed as [HH]:[MM]:["
                                           "SS], "
                                           "<offset> is expressed as +/-[HH]:["
                                           "MM] "
                                           "given with "
                                           "respect to UTC")
        if dt.tzinfo is not None:
            tzoffset_minutes = int(
                dt.tzinfo.utcoffset(None).total_seconds() / 60)

            return datetime_precision(
                dt.replace(tzinfo=FixedOffsetTimezone(offset=tzoffset_minutes,
                                                      name=None)), precision)
        else:
            return datetime_precision(
                dt.replace(tzinfo=FixedOffsetTimezone(offset=0, name=None)),
                precision)

    ########################################################################

    # Convert datetime value to datetime object
    valueDateTime.setParseAction(validate_time)

    # More General types
    value = (valueString | valueBool | valueDateTime | valueNum | valueOrderby)
    # List of values (I do not check the homogeneity of the types of values,
    # query builder will do it in a sense)
    valueList = Group(value + OneOrMore(Suppress(',') + value) +
                      Optional(Suppress(',')))

    # Fields
    singleField = Group(key + operator + value)
    listField = Group(key + (Literal('=in=') | Literal('=notin=')) + valueList)
    orderbyField = Group(key + Literal('=') + valueList)
    Field = (listField | orderbyField | singleField)

    # Fields separator
    separator = Suppress(Literal('&'))

    # General query string
    generalGrammar = SS() + Optional(Field) + ZeroOrMore(separator + Field) + \
                     Optional(separator) + SE()

    ## Parse the query string
    try:
        fields = generalGrammar.parseString(query_string)
        field_dict = fields.asDict()
        field_list = fields.asList()
    except ParseException as e:
        raise RestInputValidationError("The query string format is invalid. "
                                       "Parser returned this massage: \"{"
                                       "}.\" Please notice that the column "
                                       "number "
                                       "is counted from "
                                       "the first character of the query "
                                       "string.".format(e))

    ## return the translator instructions elaborated from the field_list
    return build_translator_parameters(field_list)