Skip to content

Commit

Permalink
Merge pull request #39 from igorkramaric/support-existance-check-ked-…
Browse files Browse the repository at this point in the history
…delegate

Support existence check for KED delegate
  • Loading branch information
jiaaro authored Jun 14, 2017
2 parents cdb627a + 8a4422c commit ec401c5
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 15 deletions.
1 change: 1 addition & 0 deletions daffodil/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .predicate import DictionaryPredicateDelegate
from .hstore_predicate import HStoreQueryDelegate
from .pretty_print import PrettyPrintDelegate
from .key_expectation_delegate import KeyExpectationDelegate
from .parser import Daffodil
29 changes: 29 additions & 0 deletions daffodil/base_delegate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from builtins import object


class BaseDaffodilDelegate(object):

def mk_any(self, children):
raise NotImplementedError()

def mk_all(self, children):
raise NotImplementedError()

def mk_not_any(self, children):
raise NotImplementedError()

def mk_not_all(self, children):
raise NotImplementedError()

def mk_test(self, test_str):
raise NotImplementedError()

def mk_comment(self, comment, is_inline):
raise NotImplementedError()

def mk_cmp(self, key, val, test):
raise NotImplementedError()

def call(self, predicate, iterable):
raise NotImplementedError()

6 changes: 4 additions & 2 deletions daffodil/hstore_predicate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
standard_library.install_aliases()
from builtins import str
from past.builtins import basestring
from builtins import object
from collections import UserString

from .base_delegate import BaseDaffodilDelegate


RE_CASE = "CASE WHEN ({0}->'{1}' ~ E'"
RE_THEN = "') THEN "
RE_ELSE = "ELSE -2147483648 END"
Expand Down Expand Up @@ -38,7 +40,7 @@ def make_sql_array(*strings):
",".join(escape_string_sql(s) for s in strings)
)

class HStoreQueryDelegate(object):
class HStoreQueryDelegate(BaseDaffodilDelegate):

def __init__(self, hstore_field_name):
self.field = hstore_field_name
Expand Down
59 changes: 59 additions & 0 deletions daffodil/key_expectation_delegate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
from .base_delegate import BaseDaffodilDelegate


class KeyExpectationDelegate(BaseDaffodilDelegate):
"""
Determines which keys in a daffodil are required in data dictionaries
in order to match and which keys have to be omitted to match.
Useful for making inferences like detecting when a key would never be set
but should (or would be but shouldn't).
"""
def _mk_group(self, children, negate):
expect_present = set()
expect_omitted = set()

for child in children:
if negate:
child = child[::-1]
expect_present |= child[0]
expect_omitted |= child[1]

# if we expect a key to be present we can't also expect it not to be
expect_omitted -= expect_present

return expect_present, expect_omitted

def mk_any(self, children):
return self._mk_group(children, False)

def mk_all(self, children):
return self._mk_group(children, False)

def mk_not_any(self, children):
return self._mk_group(children, True)

def mk_not_all(self, children):
return self._mk_group(children, True)

def mk_test(self, test_str):
if test_str != "?=":
return test_str

def test_fn(k, v, t):
return v

test_fn.is_datapoint_test = True
test_fn.test_str = test_str

return test_fn

def mk_comment(self, comment, is_inline):
return set(), set()

def mk_cmp(self, key, val, test):
existance = getattr(test, "is_datapoint_test", False)

if existance and val is False:
return set(), {key}
return {key}, set()
7 changes: 5 additions & 2 deletions daffodil/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,11 +342,14 @@ def read_quoted_string(self):

class Daffodil(object):
def __init__(self, source, delegate=DictionaryPredicateDelegate()):
parse_result = DaffodilParser(source)
if isinstance(source, DaffodilParser):
self.parse_result = source
else:
self.parse_result = DaffodilParser(source)

self.keys = set()
self.delegate = delegate
self.predicate = self.make_predicate(parse_result.tokens)
self.predicate = self.make_predicate(self.parse_result.tokens)

def _handle_group(self, parent, children):
lookup = {
Expand Down
4 changes: 2 additions & 2 deletions daffodil/predicate.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from builtins import filter
from past.builtins import basestring
from builtins import object
import operator as op

from .base_delegate import BaseDaffodilDelegate


class DictionaryPredicateDelegate(object):
class DictionaryPredicateDelegate(BaseDaffodilDelegate):
def _mk_any_all(self, children, any_all):
return lambda data_point: any_all(
predicate(data_point)
Expand Down
5 changes: 3 additions & 2 deletions daffodil/pretty_print.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
standard_library.install_aliases()
from builtins import str
from past.builtins import basestring
from builtins import object
from collections import UserList

from .base_delegate import BaseDaffodilDelegate


def to_daffodil_primitive(val):
if isinstance(val, list):
Expand Down Expand Up @@ -139,7 +140,7 @@ def format_std(self, children):
)


class PrettyPrintDelegate(object):
class PrettyPrintDelegate(BaseDaffodilDelegate):
def __init__(self, dense=True):
self.dense = dense

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='daffodil',
version='0.3.16',
version='0.3.17',
author='James Robert',
description='A Super-simple DSL for filtering datasets',
license='MIT',
Expand Down
67 changes: 61 additions & 6 deletions test/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from daffodil import Daffodil, PrettyPrintDelegate
from daffodil import (
Daffodil,
KeyExpectationDelegate, DictionaryPredicateDelegate,
HStoreQueryDelegate, PrettyPrintDelegate
)
from daffodil.exceptions import ParseError


Expand All @@ -33,12 +37,19 @@ def filter(self, daff_src):
return Daffodil(daff_src)(self.d)


class ParserGrammarTypesTests(BaseTest):
class ParserGrammarTypesTests(unittest.TestCase):
def parse(self, daff_src, delegate):
return Daffodil(daff_src, delegate=delegate)

def test_existence_doesnt_expect_string(self):
with self.assertRaises(ValueError):
self.filter('whatever ?= "true"')
self.filter('whatever ?= "False"')
self.filter('whatever ?= "any string"')
for delegate in [
HStoreQueryDelegate(hstore_field_name="dummy_name"),
DictionaryPredicateDelegate(), KeyExpectationDelegate()
]:
with self.assertRaises(ValueError):
self.parse('whatever ?= "true"', delegate)
self.parse('whatever ?= "False"', delegate)
self.parse('whatever ?= "any string"', delegate)


class SATDataTests(BaseTest):
Expand Down Expand Up @@ -775,6 +786,50 @@ def test_not_matching(self):
self.assertFalse(daff.predicate(self.data))


class KeyExpectationTests(unittest.TestCase):

def assert_daffodil_expectations(self, dafltr, present=set(), omitted=set()):
daff = Daffodil(dafltr, delegate=KeyExpectationDelegate())
daff_expected_present, daff_expected_omitted = daff.predicate
self.assertEqual(daff_expected_present, present)
self.assertEqual(daff_expected_omitted, omitted)


def test_key_expectations(self):
self.assert_daffodil_expectations(
"x = 1, y = true",
present={"x", "y"}
)
self.assert_daffodil_expectations(
"x ?= true, y ?= false",
present={"x"}, omitted={"y"}
)
self.assert_daffodil_expectations(
"!{x ?= true, y ?= false}",
present={"y"}, omitted={"x"}
)
self.assert_daffodil_expectations(
"""
!{
[
x ?= true
y ?= false
]
![
z = "a"
{
a = 1
b != 2
c > 10
d < 9
}
]
}
a ?= false
""",
present={"a", "b", "c", "d", "y", "z"}, omitted={"x"}
)


# input, expected_dense, expected_pretty
PRETTY_PRINT_EXPECTATIONS = (
Expand Down

0 comments on commit ec401c5

Please sign in to comment.