-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add translator between a predefined grammar and django's filter lookups #95
base: master
Are you sure you want to change the base?
Changes from all commits
be2b765
ac849e2
3b903f0
8cfa140
5008f28
e1b96a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
start : query | ||
| expression | ||
|
||
query : "("* expression ")"* | ||
| query CONNECTOR query | ||
|
||
expression : computed OPERATOR atom | ||
|
||
computed : FIELD | ||
| computed "." computed | ||
|
||
atom : "True" -> true | ||
| "False" -> false | ||
| NUMBER | ||
| WORD | ||
| ESCAPED_STRING -> string | ||
|
||
CONNECTOR : "and" | "or" | ||
FIELD : (WORD | "_")+ | ||
OPERATOR : "<" | "<=" | "==" | ">=" | ">" | "null" | "is" | ||
|
||
|
||
%import common.WORD | ||
%import common.NUMBER | ||
%import common.ESCAPED_STRING | ||
%ignore " " | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. missing blank line |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
from lark import Lark, Transformer | ||
|
||
|
||
class DjangoQueriesTransformer(Transformer): | ||
""" | ||
Applies multiple transformations over the parsed tree | ||
""" | ||
def start(self, tree): | ||
return tree[0] | ||
|
||
def OPERATOR(self, tree): | ||
operator_map = { | ||
">": "__gt", | ||
"<": "__lt", | ||
"==": "__eq", | ||
"is": "", | ||
"null": "__isnull", | ||
} | ||
Comment on lines
+12
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same question here: Can you change |
||
return operator_map[tree.value] | ||
|
||
def atom(self, a): | ||
(a,) = a | ||
return a.value | ||
|
||
def query(self, tree): | ||
if len(tree) == 3: | ||
return {**tree[0], **tree[2]} | ||
else: | ||
return tree[0] | ||
|
||
def expression(self, tree): | ||
return {"".join(tree[:2]): tree[2]} | ||
|
||
def computed(self, tree): | ||
return "__".join(tree) | ||
|
||
def string(self, s): | ||
(s,) = s | ||
return s.strip('"') | ||
|
||
def true(self, _): | ||
return True | ||
|
||
def false(self, _): | ||
return False | ||
|
||
|
||
def translate_query(expression, grammar): | ||
lexer = Lark(grammar) | ||
|
||
parse_tree = lexer.parse(expression, start="start") | ||
query_dict = DjangoQueriesTransformer( | ||
visit_tokens=True | ||
).transform(parse_tree) | ||
return query_dict |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
from pathlib import Path | ||
|
||
from django.test import TestCase | ||
from drip.models import Drip | ||
|
||
from .parser import translate_query | ||
|
||
|
||
class GrammarTest(TestCase): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same change here |
||
def test_grammar(self): | ||
grammar = Path(Path(__file__).parent, "grammar.lark").open() | ||
grammar = "".join(grammar.readlines()) | ||
|
||
Drip(name="Nacho's Drip").save() | ||
Drip(name="Nice Drip").save() | ||
Drip(name="Not so nice").save() | ||
|
||
input_expression = "(enabled is False)" \ | ||
"and (sent_drips null True)" \ | ||
"and (name is \"Nacho's Drip\")" | ||
filter = translate_query(input_expression, grammar) | ||
assert Drip.objects.filter(**filter).count() == 1 | ||
|
||
input_expression = "enabled is False" | ||
filter = translate_query(input_expression, grammar) | ||
assert Drip.objects.filter(**filter).count() == 3 | ||
|
||
input_expression = "(enabled is False) and (name is \"Not so nice\")" | ||
filter = translate_query(input_expression, grammar) | ||
assert Drip.objects.filter(**filter).count() == 1 | ||
|
||
input_expression = "(enabled is False) and (sent_drips null True)" | ||
filter = translate_query(input_expression, grammar) | ||
assert Drip.objects.filter(**filter).count() == 3 | ||
|
||
input_expression = "(enabled is False) and (sent_drips null False)" | ||
filter = translate_query(input_expression, grammar) | ||
assert Drip.objects.filter(**filter).count() == 0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,7 +20,7 @@ | |
author = 'Kalil de Lima' | ||
author_email = '[email protected]' | ||
license = 'MIT' | ||
install_requires = ['Django>=2.2', 'apscheduler'] | ||
install_requires = ['Django>=2.2', 'apscheduler', 'lark-parser'] | ||
keywords = 'django drip email user query' | ||
|
||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,6 +20,7 @@ | |
'django.contrib.messages', | ||
|
||
'drip', | ||
'grammar', | ||
|
||
# testing only | ||
'credits', | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you change
"
by'
in this file?