From 2e9ab08af574c1f82531e9c7611d113f813dd08e Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 26 Jun 2019 14:29:05 +0200 Subject: [PATCH 01/10] [BEAM-7577] Allow ValueProviders in Datastore Query filters --- .../io/gcp/datastore/v1new/types.py | 29 +++++++++++++++++ .../io/gcp/datastore/v1new/types_test.py | 32 +++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py index c80fe0486b27..4290f9621f05 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py @@ -25,6 +25,7 @@ import copy +from apache_beam.options.value_provider import ValueProvider from google.cloud.datastore import entity from google.cloud.datastore import key from google.cloud.datastore import query @@ -75,6 +76,9 @@ def _to_client_query(self, client): ancestor_client_key = None if self.ancestor is not None: ancestor_client_key = self.ancestor.to_client_key() + + self.filters = self._set_runtime_filters() + return query.Query( client, kind=self.kind, project=self.project, namespace=self.namespace, ancestor=ancestor_client_key, filters=self.filters, @@ -84,6 +88,31 @@ def _to_client_query(self, client): def clone(self): return copy.copy(self) + def _set_runtime_filters(self): + """ + Extracts values from ValueProviders in `self.filters` if available + :param filters: sequence of tuple[str, str, str] or + sequence of tuple[ValueProvider, ValueProvider, ValueProvider] + :return: tuple[str, str, str] + """ + runtime_filters = [] + if not all(len(filter_tuple) == 3 for filter_tuple in self.filters): + raise TypeError('%s: filters must be a sequence of tuple[str, str, str])' + ' or sequence of tuple[ValueProvider, ValueProvider, ValueProvider]);' + ' got %r instead' + % (self.__class__.__name__, self.filters)) + + for filter_type, filter_operator, filter_value in self.filters: + if isinstance(filter_type, ValueProvider): + filter_type = filter_type.get() + if isinstance(filter_operator, ValueProvider): + filter_operator = filter_operator.get() + if isinstance(filter_value, ValueProvider): + filter_value = filter_value.get() + runtime_filters.append((filter_type, filter_operator, filter_value)) + + return runtime_filters or () + def __repr__(self): return ('' % ( diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py index 7ba82c546b1c..e4db81243218 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py @@ -30,6 +30,7 @@ from apache_beam.io.gcp.datastore.v1new.types import Entity from apache_beam.io.gcp.datastore.v1new.types import Key from apache_beam.io.gcp.datastore.v1new.types import Query + from apache_beam.options.value_provider import StaticValueProvider # TODO(BEAM-4543): Remove TypeError once googledatastore dependency is removed. except (ImportError, TypeError): client = None @@ -134,6 +135,37 @@ def testQuery(self): logging.info('query: %s', q) # Test __repr__() + def testRuntimeFilters(self): + filter_list = [ + [(StaticValueProvider('property_name'), # Filter 1 + StaticValueProvider('='), + StaticValueProvider('value'))], + [(StaticValueProvider('property_name'), # Filter 2(1) + StaticValueProvider('='), + StaticValueProvider('value')), + ('property_name', '=', 'value')], # Filter 2(2) + () # Filter 3 + ] + for filters in filter_list: + projection = ['f1', 'f2'] + order = projection + distinct_on = projection + ancestor_key = Key(['kind', 'id'], project=self._PROJECT) + q = Query(kind='kind', project=self._PROJECT, namespace=self._NAMESPACE, + ancestor=ancestor_key, filters=filters, projection=projection, + order=order, distinct_on=distinct_on) + cq = q._to_client_query(self._test_client) + self.assertEqual(self._PROJECT, cq.project) + self.assertEqual(self._NAMESPACE, cq.namespace) + self.assertEqual('kind', cq.kind) + self.assertEqual(ancestor_key.to_client_key(), cq.ancestor) + self.assertEqual(filters, cq.filters) + self.assertEqual(projection, cq.projection) + self.assertEqual(order, cq.order) + self.assertEqual(distinct_on, cq.distinct_on) + + logging.info('query: %s', q) # Test __repr__() + def testQueryEmptyNamespace(self): # Test that we can pass a namespace of None. self._test_client.namespace = None From 993fbb998e8f2ee78964e48890ca19268dac4853 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 26 Jun 2019 14:59:17 +0200 Subject: [PATCH 02/10] [BEAM-7577] Fixed types_test logic --- .../apache_beam/io/gcp/datastore/v1new/types.py | 3 +-- .../apache_beam/io/gcp/datastore/v1new/types_test.py | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py index 4290f9621f05..210880a4dec3 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py @@ -97,8 +97,7 @@ def _set_runtime_filters(self): """ runtime_filters = [] if not all(len(filter_tuple) == 3 for filter_tuple in self.filters): - raise TypeError('%s: filters must be a sequence of tuple[str, str, str])' - ' or sequence of tuple[ValueProvider, ValueProvider, ValueProvider]);' + raise TypeError('%s: filters must be a sequence of tuple with length=3' ' got %r instead' % (self.__class__.__name__, self.filters)) diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py index e4db81243218..3e5103a05baa 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py @@ -137,12 +137,12 @@ def testQuery(self): def testRuntimeFilters(self): filter_list = [ - [(StaticValueProvider('property_name'), # Filter 1 - StaticValueProvider('='), - StaticValueProvider('value'))], - [(StaticValueProvider('property_name'), # Filter 2(1) - StaticValueProvider('='), - StaticValueProvider('value')), + [(StaticValueProvider(str, 'property_name'), # Filter 1 + StaticValueProvider(str, '='), + StaticValueProvider(str, 'value'))], + [(StaticValueProvider(str, 'property_name'), # Filter 2(1) + StaticValueProvider(str, '='), + StaticValueProvider(str, 'value')), ('property_name', '=', 'value')], # Filter 2(2) () # Filter 3 ] From f5aa91900fae017fdde6310a854829a458b7ada7 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 26 Jun 2019 15:29:39 +0200 Subject: [PATCH 03/10] [BEAM-7577] Removed unnecessary test case --- sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py index 3e5103a05baa..c7eebddad27f 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py @@ -144,7 +144,6 @@ def testRuntimeFilters(self): StaticValueProvider(str, '='), StaticValueProvider(str, 'value')), ('property_name', '=', 'value')], # Filter 2(2) - () # Filter 3 ] for filters in filter_list: projection = ['f1', 'f2'] From 32256e8dcd5a6858996a201b11e205d2650ae6f8 Mon Sep 17 00:00:00 2001 From: Elias Date: Wed, 26 Jun 2019 16:12:24 +0200 Subject: [PATCH 04/10] [BEAM-7577] Fixed Py27 linting issues --- sdks/python/apache_beam/io/gcp/datastore/v1new/types.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py index 210880a4dec3..2cc7525ef31c 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py @@ -25,11 +25,12 @@ import copy -from apache_beam.options.value_provider import ValueProvider from google.cloud.datastore import entity from google.cloud.datastore import key from google.cloud.datastore import query +from apache_beam.options.value_provider import ValueProvider + __all__ = ['Query', 'Key', 'Entity'] From 463f7f971af0ef1445a734b4fcbc6b31c45ee3ef Mon Sep 17 00:00:00 2001 From: Elias Date: Thu, 27 Jun 2019 16:13:20 +0200 Subject: [PATCH 05/10] [BEAM-7577] Simplified ValueProvider value extraction. Added documentation. Fixed tests --- .../io/gcp/datastore/v1new/types.py | 33 +++----------- .../io/gcp/datastore/v1new/types_test.py | 44 +++++++------------ 2 files changed, 23 insertions(+), 54 deletions(-) diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py index 2cc7525ef31c..ed1e57595654 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py @@ -46,8 +46,10 @@ def __init__(self, kind=None, project=None, namespace=None, ancestor=None, ancestor: (:class:`~apache_beam.io.gcp.datastore.v1new.types.Key`) (Optional) key of the ancestor to which this query's results are restricted. - filters: (sequence of tuple[str, str, str]) Property filters applied by - this query. The sequence is ``(property_name, operator, value)``. + filters: (sequence of tuple[str, str, str], + sequence of ValueProvider[tuple[str, str, str]]) + Property filters applied by this query. + The sequence is ``(property_name, operator, value)``. projection: (sequence of string) fields returned as part of query results. order: (sequence of string) field names used to order query results. Prepend ``-`` to a field name to sort it in descending order. @@ -78,7 +80,8 @@ def _to_client_query(self, client): if self.ancestor is not None: ancestor_client_key = self.ancestor.to_client_key() - self.filters = self._set_runtime_filters() + self.filters = [filter.get() if isinstance(filter, ValueProvider) + else filter for filter in self.filters] or () return query.Query( client, kind=self.kind, project=self.project, namespace=self.namespace, @@ -89,30 +92,6 @@ def _to_client_query(self, client): def clone(self): return copy.copy(self) - def _set_runtime_filters(self): - """ - Extracts values from ValueProviders in `self.filters` if available - :param filters: sequence of tuple[str, str, str] or - sequence of tuple[ValueProvider, ValueProvider, ValueProvider] - :return: tuple[str, str, str] - """ - runtime_filters = [] - if not all(len(filter_tuple) == 3 for filter_tuple in self.filters): - raise TypeError('%s: filters must be a sequence of tuple with length=3' - ' got %r instead' - % (self.__class__.__name__, self.filters)) - - for filter_type, filter_operator, filter_value in self.filters: - if isinstance(filter_type, ValueProvider): - filter_type = filter_type.get() - if isinstance(filter_operator, ValueProvider): - filter_operator = filter_operator.get() - if isinstance(filter_value, ValueProvider): - filter_value = filter_value.get() - runtime_filters.append((filter_type, filter_operator, filter_value)) - - return runtime_filters or () - def __repr__(self): return ('' % ( diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py index c7eebddad27f..44398898fa98 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py @@ -135,35 +135,25 @@ def testQuery(self): logging.info('query: %s', q) # Test __repr__() - def testRuntimeFilters(self): - filter_list = [ - [(StaticValueProvider(str, 'property_name'), # Filter 1 - StaticValueProvider(str, '='), - StaticValueProvider(str, 'value'))], - [(StaticValueProvider(str, 'property_name'), # Filter 2(1) - StaticValueProvider(str, '='), - StaticValueProvider(str, 'value')), - ('property_name', '=', 'value')], # Filter 2(2) - ] - for filters in filter_list: - projection = ['f1', 'f2'] - order = projection - distinct_on = projection - ancestor_key = Key(['kind', 'id'], project=self._PROJECT) + def testValueProviderFilters(self): + self.vp_filters = [[StaticValueProvider(tuple, + ('property_name', '=', 'value'))], + [StaticValueProvider(tuple, + ('property_name', '=', 'value')), + ('property_name', '=', 'value')], + ] + self.expected_filters = [[('property_name', '=', 'value')], + [('property_name', '=', 'value'), + ('property_name', '=', 'value')], + ] + + for vp_filter, exp_filter in zip(self.vp_filters, self.expected_filters): q = Query(kind='kind', project=self._PROJECT, namespace=self._NAMESPACE, - ancestor=ancestor_key, filters=filters, projection=projection, - order=order, distinct_on=distinct_on) + filters=vp_filter) cq = q._to_client_query(self._test_client) - self.assertEqual(self._PROJECT, cq.project) - self.assertEqual(self._NAMESPACE, cq.namespace) - self.assertEqual('kind', cq.kind) - self.assertEqual(ancestor_key.to_client_key(), cq.ancestor) - self.assertEqual(filters, cq.filters) - self.assertEqual(projection, cq.projection) - self.assertEqual(order, cq.order) - self.assertEqual(distinct_on, cq.distinct_on) - - logging.info('query: %s', q) # Test __repr__() + self.assertEqual(exp_filter, cq.filters) + + print('query: %s', q) # Test __repr__() def testQueryEmptyNamespace(self): # Test that we can pass a namespace of None. From 31cce7d8be71ef1c63074eeb9ca38673092d0687 Mon Sep 17 00:00:00 2001 From: Elias Date: Thu, 27 Jun 2019 17:00:43 +0200 Subject: [PATCH 06/10] [BEAM-7577] Fixed linting issues --- sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py index 44398898fa98..2ab226c03ba4 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py @@ -141,11 +141,11 @@ def testValueProviderFilters(self): [StaticValueProvider(tuple, ('property_name', '=', 'value')), ('property_name', '=', 'value')], - ] + ] self.expected_filters = [[('property_name', '=', 'value')], [('property_name', '=', 'value'), ('property_name', '=', 'value')], - ] + ] for vp_filter, exp_filter in zip(self.vp_filters, self.expected_filters): q = Query(kind='kind', project=self._PROJECT, namespace=self._NAMESPACE, From 6dc5fe8833b4a725b2bde8c6a051f5bc586c6117 Mon Sep 17 00:00:00 2001 From: Elias Date: Thu, 27 Jun 2019 21:19:59 +0200 Subject: [PATCH 07/10] [BEAM-7577] Changed typoed print function to logging --- sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py index 2ab226c03ba4..04c885297291 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py @@ -153,7 +153,7 @@ def testValueProviderFilters(self): cq = q._to_client_query(self._test_client) self.assertEqual(exp_filter, cq.filters) - print('query: %s', q) # Test __repr__() + logging.info('query: %s', q) # Test __repr__() def testQueryEmptyNamespace(self): # Test that we can pass a namespace of None. From a475a7d45731f58aab5125433c69f5511c7b153e Mon Sep 17 00:00:00 2001 From: Elias Date: Fri, 26 Jul 2019 14:06:01 +0200 Subject: [PATCH 08/10] Reverted changes. Added ValueProvider support for query splitter check --- .../io/gcp/datastore/v1new/query_splitter.py | 4 +++ .../io/gcp/datastore/v1new/types.py | 30 +++++++++++++++++-- .../io/gcp/datastore/v1new/types_test.py | 17 +++++++---- 3 files changed, 42 insertions(+), 9 deletions(-) diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/query_splitter.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/query_splitter.py index 4f8be839df67..3c5cb44ff13a 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/query_splitter.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/query_splitter.py @@ -27,6 +27,7 @@ from builtins import round from apache_beam.io.gcp.datastore.v1new import types +from apache_beam.options.value_provider import ValueProvider __all__ = ['QuerySplitterError', 'SplitNotPossibleError', 'get_splits'] @@ -104,6 +105,9 @@ def validate_split(query): raise SplitNotPossibleError('Query cannot have a limit set.') for filter in query.filters: + if isinstance(filter, ValueProvider): + if filter[1].get() in ['<', '<=', '>', '>=']: + raise SplitNotPossibleError('Query cannot have any inequality filters.') if filter[1] in ['<', '<=', '>', '>=']: raise SplitNotPossibleError('Query cannot have any inequality filters.') diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py index ed1e57595654..b89425c69f0a 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/types.py @@ -47,7 +47,8 @@ def __init__(self, kind=None, project=None, namespace=None, ancestor=None, (Optional) key of the ancestor to which this query's results are restricted. filters: (sequence of tuple[str, str, str], - sequence of ValueProvider[tuple[str, str, str]]) + sequence of + tuple[ValueProvider(str), ValueProvider(str), ValueProvider(str)]) Property filters applied by this query. The sequence is ``(property_name, operator, value)``. projection: (sequence of string) fields returned as part of query results. @@ -80,8 +81,7 @@ def _to_client_query(self, client): if self.ancestor is not None: ancestor_client_key = self.ancestor.to_client_key() - self.filters = [filter.get() if isinstance(filter, ValueProvider) - else filter for filter in self.filters] or () + self.filters = self._set_runtime_filters() return query.Query( client, kind=self.kind, project=self.project, namespace=self.namespace, @@ -89,6 +89,30 @@ def _to_client_query(self, client): projection=self.projection, order=self.order, distinct_on=self.distinct_on) + def _set_runtime_filters(self): + """ + Extracts values from ValueProviders in `self.filters` if available + :param filters: sequence of tuple[str, str, str] or + sequence of tuple[ValueProvider, ValueProvider, ValueProvider] + :return: tuple[str, str, str] + """ + runtime_filters = [] + if not all(len(filter_tuple) == 3 for filter_tuple in self.filters): + raise TypeError('%s: filters must be a sequence of tuple with length=3' + ' got %r instead' + % (self.__class__.__name__, self.filters)) + + for filter_type, filter_operator, filter_value in self.filters: + if isinstance(filter_type, ValueProvider): + filter_type = filter_type.get() + if isinstance(filter_operator, ValueProvider): + filter_operator = filter_operator.get() + if isinstance(filter_value, ValueProvider): + filter_value = filter_value.get() + runtime_filters.append((filter_type, filter_operator, filter_value)) + + return runtime_filters or () + def clone(self): return copy.copy(self) diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py index 04c885297291..17cb0cc95d1f 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/types_test.py @@ -136,12 +136,17 @@ def testQuery(self): logging.info('query: %s', q) # Test __repr__() def testValueProviderFilters(self): - self.vp_filters = [[StaticValueProvider(tuple, - ('property_name', '=', 'value'))], - [StaticValueProvider(tuple, - ('property_name', '=', 'value')), - ('property_name', '=', 'value')], - ] + self.vp_filters = [ + [( + StaticValueProvider(str, 'property_name'), + StaticValueProvider(str, '='), + StaticValueProvider(str, 'value'))], + [( + StaticValueProvider(str, 'property_name'), + StaticValueProvider(str, '='), + StaticValueProvider(str, 'value')), + ('property_name', '=', 'value')], + ] self.expected_filters = [[('property_name', '=', 'value')], [('property_name', '=', 'value'), ('property_name', '=', 'value')], From 539a4eb64401342fe602d44d53cf3a7d55cdbeca Mon Sep 17 00:00:00 2001 From: Elias Date: Fri, 26 Jul 2019 15:29:11 +0200 Subject: [PATCH 09/10] Fixed query splitter logic --- .../python/apache_beam/io/gcp/datastore/v1new/query_splitter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/query_splitter.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/query_splitter.py index 3c5cb44ff13a..9f476b886c7e 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/query_splitter.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/query_splitter.py @@ -105,7 +105,7 @@ def validate_split(query): raise SplitNotPossibleError('Query cannot have a limit set.') for filter in query.filters: - if isinstance(filter, ValueProvider): + if isinstance(filter[1], ValueProvider): if filter[1].get() in ['<', '<=', '>', '>=']: raise SplitNotPossibleError('Query cannot have any inequality filters.') if filter[1] in ['<', '<=', '>', '>=']: From 749a8cc9cf3d7fca62c7d803a49b97f32c1b450a Mon Sep 17 00:00:00 2001 From: Udi Meiri Date: Thu, 1 Aug 2019 17:32:06 -0700 Subject: [PATCH 10/10] reduced minor code duplication --- .../apache_beam/io/gcp/datastore/v1new/query_splitter.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sdks/python/apache_beam/io/gcp/datastore/v1new/query_splitter.py b/sdks/python/apache_beam/io/gcp/datastore/v1new/query_splitter.py index 9f476b886c7e..f1420fdcf653 100644 --- a/sdks/python/apache_beam/io/gcp/datastore/v1new/query_splitter.py +++ b/sdks/python/apache_beam/io/gcp/datastore/v1new/query_splitter.py @@ -106,9 +106,10 @@ def validate_split(query): for filter in query.filters: if isinstance(filter[1], ValueProvider): - if filter[1].get() in ['<', '<=', '>', '>=']: - raise SplitNotPossibleError('Query cannot have any inequality filters.') - if filter[1] in ['<', '<=', '>', '>=']: + filter_operator = filter[1].get() + else: + filter_operator = filter[1] + if filter_operator in ['<', '<=', '>', '>=']: raise SplitNotPossibleError('Query cannot have any inequality filters.')