<commit_before># -*- coding: utf-8 -*-

"""
    This module implements a Python-to-SQLAlchemy syntax parser.
    Allows the SQLAlchemy data-layer to seamlessy respond to a
    Python-like query.

    :copyright: (c) 2013 by <NAME> and <NAME> (Tefnet)
    :license: BSD, see LICENSE for more details.
"""
import re
import ast
import operator as sqla_op
import json

from eve.utils import str_to_date
from sqlalchemy.ext.associationproxy import AssociationProxy
from sqlalchemy.sql import expression as sqla_exp


class ParseError(ValueError):
    pass


def parse_dictionary(filter_dict, model):
    """
    Parse a dictionary into a list of SQLAlchemy BinaryExpressions to be used
    in query filters.

    :param filter_dict: Dictionary to convert
    :param model: SQLAlchemy model class used to create the BinaryExpressions
    :return list: List of conditions as SQLAlchemy BinaryExpressions
    """
    if len(filter_dict) == 0:
        return []

    conditions = []

    for k, v in filter_dict.items():
        # firts let's check with the expression parser

        try:
            conditions += parse('{0}{1}'.format(k, v), model)
        except ParseError:
            pass
        else:
            continue

        attr = getattr(model, k)

        if isinstance(attr, AssociationProxy):
            conditions.append(attr.contains(v))

        elif hasattr(attr, 'property') and \
                hasattr(attr.property, 'remote_side'):  # a relation
            for fk in attr.property.remote_side:
                conditions.append(sqla_op.eq(fk, v))

        else:
            try:
                new_o, v = parse_sqla_operators(v)
                new_filter = getattr(attr, new_o)(v)
            except (TypeError, ValueError):  # json/sql parse error
                if isinstance(v, list):  # we have an array
                    new_filter = attr.in_(v)
                else:
                    new_filter = sqla_op.eq(attr, v)
            conditions.append(new_filter)

    return conditions


def parse_sqla_operators(expression):
    """
    Parse expressions like:
        like('%john%')
        ilike('john%')
        in_(['a','b'])
    """
    m = re.match(r"(?P<operator>\w+)\((?P<value>.+)\)", expression)
    if m:
        o = m.group('operator')
        v = json.loads(m.group('value'))
        return o, v


def parse(expression, model):
    """
    Given a python-like conditional statement, returns the equivalent
    SQLAlchemy-like query expression. Conditional and boolean operators
    (==, <=, >=, !=, >, <) are supported.
    """
    v = SQLAVisitor(model)
    try:
        parsed_expr = ast.parse(expression)
    except SyntaxError:
        raise ParseError("Can't parse expression '{}'".format(expression))

    v.visit(parsed_expr)
    return v.sqla_query


def parse_sorting(model, query, key, order=1, expression=None):
    """
    Sorting parser that works with embedded resources and sql expressions<commit_msg>use sqlalchemy operator<commit_after># -*- coding: utf-8 -*-

"""
    This module implements a Python-to-SQLAlchemy syntax parser.
    Allows the SQLAlchemy data-layer to seamlessy respond to a
    Python-like query.

    :copyright: (c) 2013 by <NAME> and <NAME> (Tefnet)
    :license: BSD, see LICENSE for more details.
"""
import re
import ast
import operator as sqla_op
import json

from eve.utils import str_to_date
from sqlalchemy.ext.associationproxy import AssociationProxy
from sqlalchemy.sql import expression as sqla_exp


class ParseError(ValueError):
    pass


def parse_dictionary(filter_dict, model):
    """
    Parse a dictionary into a list of SQLAlchemy BinaryExpressions to be used
    in query filters.

    :param filter_dict: Dictionary to convert
    :param model: SQLAlchemy model class used to create the BinaryExpressions
    :return list: List of conditions as SQLAlchemy BinaryExpressions
    """
    if len(filter_dict) == 0:
        return []

    conditions = []

    for k, v in filter_dict.items():
        # firts let's check with the expression parser

        try:
            conditions += parse('{0}{1}'.format(k, v), model)
        except ParseError:
            pass
        else:
            continue

        attr = getattr(model, k)

        if isinstance(attr, AssociationProxy):
            conditions.append(attr.contains(v))

        elif hasattr(attr, 'property') and \
                hasattr(attr.property, 'remote_side'):  # a relation
            for fk in attr.property.remote_side:
                conditions.append(sqla_op.eq(fk, v))

        else:
            try:
                new_op, v = parse_sqla_operators(v)
                new_filter = attr.op(new_op)(v)
            except (TypeError, ValueError):  # json/sql parse error
                if isinstance(v, list):  # we have an array
                    new_filter = attr.in_(v)
                else:
                    new_filter = sqla_op.eq(attr, v)
            conditions.append(new_filter)
    return conditions


def parse_sqla_operators(expression):
    """
    Parse expressions like:
        like('%john%')
        ilike('john%')
        similar to('%(ohn|acob)')
        in_(['a','b'])
    """
    m = re.match(r"(?P<operator>[\w\s]+)\(+(?P<value>.+)\)+", expression)
    if m:
        o = m.group('operator')
        v = json.loads(m.group('value'))
        return o, v


def parse(expression, model):
    """
    Given a python-like conditional statement, returns the equivalent
    SQLAlchemy-like query expression. Conditional and boolean operators
    (==, <=, >=, !=, >, <) are supported.
    """
    v = SQLAVisitor(model)
    try:
        parsed_expr = ast.parse(expression)
    except SyntaxError:
        raise ParseError("Can't parse expression '{}'".format(expression))

    v.visit(parsed_expr)
    return v.sqla_query


def parse_sorting(model, query, key, order=1, expression=None):
    """
    Sorting parser that works with embedded resources and sql expressions
{'fraction_non_alphanumeric': 0.10671804322332049, 'fraction_numerical': 0.003015580499246105, 'mean_word_length': 2.4212034383954153, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 15, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5490516', 'n_tokens_mistral': 1798, 'n_tokens_neox': 1708, 'n_words': 508}