Skip to content

Commit

Permalink
Merge pull request #5413 from hypothesis/remove-unused-stream-filter-…
Browse files Browse the repository at this point in the history
…features

Remove unused and untested code from h/streamer/filter.py
  • Loading branch information
robertknight committed Nov 8, 2018
2 parents 69871ce + abf5244 commit 41a7ed1
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 147 deletions.
180 changes: 36 additions & 144 deletions h/streamer/filter.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# -*- coding: utf-8 -*-

from __future__ import unicode_literals
import copy
import operator

import unicodedata

from jsonpointer import resolve_pointer
Expand All @@ -11,29 +10,28 @@
SCHEMA = {
"type": "object",
"properties": {
"name": {"type": "string", "optional": True},
# Ignored, but kept here for backwards compatibility.
"match_policy": {
"type": "string",
"enum": ["include_any", "include_all",
"exclude_any", "exclude_all"]
"enum": ["include_any"]
},

# Ignored, but kept here for backwards compatibility.
"actions": {
"create": {"type": "boolean", "default": True},
"update": {"type": "boolean", "default": True},
"delete": {"type": "boolean", "default": True},
},

"clauses": {
"type": "array",
"items": {
"field": {"type": "string", "format": "json-pointer"},
"operator": {
"type": "string",
"enum": ["equals", "matches", "lt", "le", "gt", "ge",
"one_of", "first_of", "match_of",
"lene", "leng", "lenge", "lenl", "lenle"]
"enum": ["equals", "one_of"]
},
"value": "object",
"options": {"type": "object", "default": {}}
}
},
},
Expand All @@ -45,157 +43,51 @@ class FilterHandler(object):
def __init__(self, filter_json):
self.filter = filter_json

# operators
operators = {
'equals': 'eq',
'matches': 'contains',
'lt': 'lt',
'le': 'le',
'gt': 'gt',
'ge': 'ge',
'one_of': 'contains',
'first_of': 'first_of',
'match_of': 'match_of',
'lene': 'lene',
'leng': 'leng',
'lenge': 'lenge',
'lenl': 'lenl',
'lenle': 'lenle',
}

def evaluate_clause(self, clause, target):
if isinstance(clause['field'], list):
for field in clause['field']:
copied = copy.deepcopy(clause)
copied['field'] = field
result = self.evaluate_clause(copied, target)
if result:
return True
field_value = resolve_pointer(target, clause['field'], None)
if field_value is None:
return False

cval = clause['value']
fval = field_value

if isinstance(cval, list):
tval = []
for cv in cval:
tval.append(uni_fold(cv))
cval = tval
else:
field_value = resolve_pointer(target, clause['field'], None)
if field_value is None:
return False

cval = clause['value']
fval = field_value

if isinstance(cval, list):
tval = []
for cv in cval:
tval.append(uni_fold(cv))
cval = tval
else:
cval = uni_fold(cval)
cval = uni_fold(cval)

if isinstance(fval, list):
tval = []
for fv in fval:
tval.append(uni_fold(fv))
fval = tval
else:
fval = uni_fold(fval)

if clause['operator'] == 'one_of':
if isinstance(fval, list):
tval = []
for fv in fval:
tval.append(uni_fold(fv))
fval = tval
else:
fval = uni_fold(fval)

reversed_order = False
# Determining operator order
# Normal order: field_value, clause['value']
# i.e. condition created > 2000.01.01
# Here clause['value'] = '2001.01.01'.
# The field_value is target['created']
# So the natural order is: ge(field_value, clause['value']

# But!
# Reversed operator order for contains (b in a)
if isinstance(cval, list) or isinstance(fval, list):
if clause['operator'] in ['one_of', 'matches']:
reversed_order = True
# But not in every case. (i.e. tags matches 'b')
# Here field_value is a list, because an annotation can
# have many tags.
if isinstance(field_value, list):
reversed_order = False

if reversed_order:
lval = cval
rval = fval
# Test whether a query value appears in a list of field values.
return cval in fval
else:
lval = fval
rval = cval

op = getattr(operator, self.operators[clause['operator']])
return op(lval, rval)
# Test whether a field value appears in a list of candidates.
return fval in cval
else:
return fval == cval

# match_policies
def include_any(self, target):
for clause in self.filter['clauses']:
if self.evaluate_clause(clause, target):
return True
return False

def include_all(self, target):
for clause in self.filter['clauses']:
if not self.evaluate_clause(clause, target):
return False
return True

def exclude_all(self, target):
for clause in self.filter['clauses']:
if not self.evaluate_clause(clause, target):
return True
return False

def exclude_any(self, target):
for clause in self.filter['clauses']:
if self.evaluate_clause(clause, target):
return False
return True

def match(self, target, action=None):
if not action or action == 'past' or action in self.filter['actions']:
if len(self.filter['clauses']) > 0:
return getattr(self, self.filter['match_policy'])(target)
else:
return True
if len(self.filter['clauses']) > 0:
return self.include_any(target)
else:
return False


def first_of(a, b):
return a[0] == b
setattr(operator, 'first_of', first_of) # noqa:E305


def match_of(a, b):
for subb in b:
if subb in a:
return True
return False
setattr(operator, 'match_of', match_of) # noqa:E305


def lene(a, b):
return len(a) == b
setattr(operator, 'lene', lene) # noqa:E305


def leng(a, b):
return len(a) > b
setattr(operator, 'leng', leng) # noqa:E305


def lenge(a, b):
return len(a) >= b
setattr(operator, 'lenge', lenge) # noqa:E305


def lenl(a, b):
return len(a) < b
setattr(operator, 'lenl', lenl) # noqa:E305


def lenle(a, b):
return len(a) <= b
setattr(operator, 'lenle', lenle) # noqa:E305


def uni_fold(text):
Expand Down
65 changes: 65 additions & 0 deletions tests/h/streamer/filter_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from __future__ import unicode_literals

from h.streamer.filter import FilterHandler


class TestFilterHandler(object):
def test_it_matches_uri(self):
query = {
"match_policy": "include_any",
"actions": {},
"clauses": [
{
"field": "/uri",
"operator": "one_of",
"value": ["https://example.com", "https://example.org"],
}
],
}
handler = FilterHandler(query)

ann = {"id": "123", "uri": "https://example.com"}
assert handler.match(ann) is True

ann = {"id": "123", "uri": "https://example.net"}
assert handler.match(ann) is False

def test_it_matches_id(self):
query = {
"match_policy": "include_any",
"actions": {},
"clauses": [
{
"field": "/id",
"operator": "equals",
"value": "123",
}
],
}
handler = FilterHandler(query)

ann = {"id": "123", "uri": "https://example.com"}
assert handler.match(ann) is True

ann = {"id": "456", "uri": "https://example.net"}
assert handler.match(ann) is False

def test_it_matches_parent_id(self):
query = {
"match_policy": "include_any",
"actions": {},
"clauses": [
{
"field": "/references",
"operator": "one_of",
"value": "123",
}
],
}
handler = FilterHandler(query)

ann = {"id": "abc", "uri": "https://example.com", "references": ["123"]}
assert handler.match(ann) is True

ann = {"id": "abc", "uri": "https://example.com", "references": ["456"]}
assert handler.match(ann) is False
6 changes: 3 additions & 3 deletions tests/h/streamer/websocket_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ def test_sets_socket_filter(self, socket):
message = websocket.Message(socket=socket, payload={
'filter': {
'actions': {},
'match_policy': 'include_all',
'match_policy': 'include_any',
'clauses': [{
'field': '/uri',
'operator': 'equals',
Expand All @@ -301,7 +301,7 @@ def test_expands_uris_in_uri_filter_with_session(self, expand_uri, socket):
message = websocket.Message(socket=socket, payload={
'filter': {
'actions': {},
'match_policy': 'include_all',
'match_policy': 'include_any',
'clauses': [{
'field': '/uri',
'operator': 'equals',
Expand All @@ -326,7 +326,7 @@ def test_expands_uris_using_passed_session(self, expand_uri, socket):
message = websocket.Message(socket=socket, payload={
'filter': {
'actions': {},
'match_policy': 'include_all',
'match_policy': 'include_any',
'clauses': [{
'field': '/uri',
'operator': 'equals',
Expand Down

0 comments on commit 41a7ed1

Please sign in to comment.