2010-01-05 3 views
3

Это будет «длинный». Я включаю в себя как можно больше кода и объяснений ... Я не выше метаданных, если это необходимо.Реализация логического парсера в django-запросе

Я пытаюсь реализовать логический парсер в системе запросов django. Где пользователи могут предоставлять сложные запросы против тегов, которые применяются к образцам. Это по существу часть хранилища научных образцов, где пользователи могут применять определенные теги (тип ткани, изученные болезни и т. Д.). Затем они могут создавать постоянные «корзины» образцов, определенных логическим запросом для этих тегов.

#models.py 

class Sample(models.Model): 
    name = models.CharField(max_length = 255) 


class Tag(models.Model): 
    name = models.CharField(max_length = 255) 
    samples = models.ManyToManyField(Sample) 

A quick example: 
#example data: 
Sample1 has TagA, TagB, TagC 
Sample2 has  TagB, TagC, TagD 
Sample3 has TagA,  TagC, TagD 
Sample4 has  TagB 

#example query: 
'TagB AND TagC AND NOT TagD' 

вернет образец1. Я использую сумасшедший строку-Eval хак, чтобы создать набор из Q() объектов:

def MakeQObject(expression): 
    """ 
    Takes an expression and uses a crazy string-eval hack to make the qobjects. 
    """ 
    log_set = {'AND':'&','OR':'|','NOT':'~'} 

    exp_f = [] 
    parts = expression.split() 
    #if there is a) or (then we can't use this shortcut 
    if '(' in parts or ')' in parts: 
     return None 

    for exp in parts: 
     if exp in log_set: 
      exp_f.append(log_set[exp]) 
     else: 
      exp_f.append("Q(tags__name__iexact = '%s')" % exp) 
    st = ' '.join(exp_f) 
    qobj = eval(st) 
    return qobj 

Однако, это не будет работать на все, что нуждается в сложный порядке операций или группировки по(). Учитывая те же данные примера, запрос: (TagA OR TagB) AND NOT TagD должен возвращать Sample1, Sample4, но этого не делать. Я реализовал функцию «один на один», которая может принимать один экземпляр объекта и выполнять запрос. Тем не менее, в моей фактической базе данных у меня есть ~ 40 000 образцов и ~ 400 тегов (около ~ 7 на выборку), и итерационная техника занимает ~ 4 минуты для завершения на всех образцах. Поэтому я каждую ночь вычисляю корзины, а затем просто замораживаю их в течение дня. Я беспокоюсь, что, когда я начинаю курировать больше корзин, образцов и тэгов, это будет плохо масштабироваться.

Любые предложения?

+0

Я также попробовал «факторинг» запросов типа '()' в более длинные, но эквивалентные версии без парсеров, но также не смог выполнить эту работу. – JudoWill

ответ

1

Во-первых, для повышения производительности он будет , вероятно, поможет добавить индекс в поле имени тега, так как вы используете его для запросов. Так, добавьте db_index = True в столбце:

class Tag(models.Model): 
    name = models.CharField(max_length = 255, db_index=True) 
    samples = models.ManyToManyField(Sample) 

Во-вторых, для анализа запросов пользователей я бы рекомендовал использовать один из нескольких хороших анализаторами Python на основе таких как PyParsing или PLY. Сначала это может показаться пугающим, но на самом деле это не так сложно, особенно с простой грамматикой, такой как ваш.

Если это слишком много для вас, попробуйте выполнить кастинг самостоятельно, используя руководство Fredrik Simple Top-Down Parsing in Python.

+0

Дополнительное примечание: вам нужно сбросить базу данных для db_index, чтобы ее можно было увидеть с помощью syncdb. Если вы не можете выполнить сброс, вам нужно вручную добавить индекс в базу данных или использовать инструмент миграции, например, South (http://south.aeracode.org/). –

+0

Спасибо Ван ... Думаю, я могу, вероятно, использовать их пример для простых математических выражений «http://pyparsing.wikispaces.com/file/view/fourFn.py». Просто замените «math.operations» на операции запроса, а затем пусть он позаботится о вложенности и т. Д. – JudoWill