From a2176990c8a34c3bf41f2c995cb085407cd7da9d Mon Sep 17 00:00:00 2001 From: Keiran Price Date: Thu, 5 Sep 2019 11:37:54 +0100 Subject: [PATCH 1/8] #41: Make precedence a required property --- common/database_helpers.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/common/database_helpers.py b/common/database_helpers.py index aa9903c0..43178af9 100644 --- a/common/database_helpers.py +++ b/common/database_helpers.py @@ -117,6 +117,12 @@ def execute_query(self): class QueryFilter(ABC): + @property + @abstractmethod + def precedence(self): + pass + + @abstractmethod def apply_filter(self, query): pass From cb67f076a2a45af9ef8a7e52e5cf317da2301e85 Mon Sep 17 00:00:00 2001 From: Keiran Price Date: Thu, 5 Sep 2019 11:40:17 +0100 Subject: [PATCH 2/8] #41: Add Distinct field filter and update precedence --- common/database_helpers.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/common/database_helpers.py b/common/database_helpers.py index 43178af9..875a4597 100644 --- a/common/database_helpers.py +++ b/common/database_helpers.py @@ -122,14 +122,13 @@ class QueryFilter(ABC): def precedence(self): pass - @abstractmethod def apply_filter(self, query): pass class WhereFilter(QueryFilter): - precedence = 0 + precedence = 1 def __init__(self, field, value, operation): self.field = field @@ -149,8 +148,15 @@ def apply_filter(self, query): raise BadFilterError(f" Bad operation given to where filter. operation: {self.operation}") +class DistinctFieldFilter(QueryFilter): + precedence = 0 + + def apply_filter(self, query): + pass + + class OrderFilter(QueryFilter): - precedence = 1 + precedence = 2 def __init__(self, field, direction): self.field = field @@ -166,7 +172,7 @@ def apply_filter(self, query): class SkipFilter(QueryFilter): - precedence = 2 + precedence = 3 def __init__(self, skip_value): self.skip_value = skip_value @@ -176,7 +182,7 @@ def apply_filter(self, query): class LimitFilter(QueryFilter): - precedence = 3 + precedence = 4 def __init__(self, limit_value): self.limit_value = limit_value @@ -186,7 +192,7 @@ def apply_filter(self, query): class IncludeFilter(QueryFilter): - precedence = 4 + precedence = 5 def __init__(self, included_filters): self.included_filters = included_filters @@ -227,6 +233,7 @@ class FilterOrderHandler(object): """ The FilterOrderHandler takes in filters, sorts them according to the order of operations, then applies them. """ + def __init__(self): self.filters = [] From cc2459affb724a9b6fbeca97427bb213f6c6e728 Mon Sep 17 00:00:00 2001 From: Keiran Price Date: Wed, 11 Sep 2019 09:45:45 +0100 Subject: [PATCH 3/8] #41: Add is_distinct bool to ReadQuery --- common/database_helpers.py | 1 + 1 file changed, 1 insertion(+) diff --git a/common/database_helpers.py b/common/database_helpers.py index 875a4597..303e6712 100644 --- a/common/database_helpers.py +++ b/common/database_helpers.py @@ -52,6 +52,7 @@ class ReadQuery(Query): def __init__(self, table): super().__init__(table) self.include_related_entities = False + self.is_distinct_fields_query = False def execute_query(self): self.commit_changes() From 2a496a0e082062600e813cd43eba8812fdc00f6d Mon Sep 17 00:00:00 2001 From: Keiran Price Date: Wed, 11 Sep 2019 09:46:16 +0100 Subject: [PATCH 4/8] #41: Add implementation to filter --- common/database_helpers.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/common/database_helpers.py b/common/database_helpers.py index 303e6712..2c003111 100644 --- a/common/database_helpers.py +++ b/common/database_helpers.py @@ -152,8 +152,16 @@ def apply_filter(self, query): class DistinctFieldFilter(QueryFilter): precedence = 0 + def __init__(self, fields): + self.fields = fields if type(fields) is list else [fields] # This allows single string distinct filters + def apply_filter(self, query): - pass + query.is_distinct_fields_query = True + try: + self.fields = [getattr(query.table, field) for field in self.fields] + except AttributeError: + raise BadFilterError("Bad field requested") + query.base_query = query.session.query(*self.fields).distinct() class OrderFilter(QueryFilter): From 1b76f38782e0e33bb2812e9567380f42b5b121ca Mon Sep 17 00:00:00 2001 From: Keiran Price Date: Wed, 11 Sep 2019 09:46:38 +0100 Subject: [PATCH 5/8] #41: Update Factory --- common/database_helpers.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/database_helpers.py b/common/database_helpers.py index 2c003111..b530d6ee 100644 --- a/common/database_helpers.py +++ b/common/database_helpers.py @@ -234,6 +234,8 @@ def get_query_filter(filter): return LimitFilter(filter["limit"]) elif filter_name == "include": return IncludeFilter(filter) + elif filter_name == "distinct": + return DistinctFieldFilter(filter["distinct"]) else: raise BadFilterError(f" Bad filter: {filter}") From eebf29af4826c16ef5b13cdc9d58cc500d2a6536 Mon Sep 17 00:00:00 2001 From: Keiran Price Date: Wed, 11 Sep 2019 09:52:13 +0100 Subject: [PATCH 6/8] #41: Extract include into function --- common/database_helpers.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/common/database_helpers.py b/common/database_helpers.py index b530d6ee..cbdb67c6 100644 --- a/common/database_helpers.py +++ b/common/database_helpers.py @@ -347,14 +347,25 @@ def get_rows_by_filter(table, filters): filter_handler.apply_filters(query) results = query.get_all_results() if query.include_related_entities: - for query_filter in filters: - if list(query_filter)[0].lower() == "include": - return list(map(lambda x: x.to_nested_dict(query_filter["include"]), results)) + return _get_results_with_include(filters, results) return list(map(lambda x: x.to_dict(), results)) finally: query.session.close() +def _get_results_with_include(filters, results): + """ + Given a list of entities and a list of filters, use the include filter to nest the included entities requested in + the include filter given + :param filters: The list of filters + :param results: The list of entities + :return: A list of nested dictionaries representing the entity results + """ + for query_filter in filters: + if list(query_filter)[0].lower() == "include": + return [x.to_nested_dict(query_filter["include"]) for x in results] + + def get_first_filtered_row(table, filters): """ returns the first row that matches a given filter, in a given table From 2f853683a8a13b38570d283990574d87e22659b5 Mon Sep 17 00:00:00 2001 From: Keiran Price Date: Wed, 11 Sep 2019 09:58:54 +0100 Subject: [PATCH 7/8] #41: Add and use function for distinct fields --- common/database_helpers.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/common/database_helpers.py b/common/database_helpers.py index cbdb67c6..d1b19f2d 100644 --- a/common/database_helpers.py +++ b/common/database_helpers.py @@ -346,6 +346,8 @@ def get_rows_by_filter(table, filters): filter_handler.add_filter(QueryFilterFactory.get_query_filter(query_filter)) filter_handler.apply_filters(query) results = query.get_all_results() + if query.is_distinct_fields_query: + return _get_distinct_fields_as_dicts(results) if query.include_related_entities: return _get_results_with_include(filters, results) return list(map(lambda x: x.to_dict(), results)) @@ -366,6 +368,20 @@ def _get_results_with_include(filters, results): return [x.to_nested_dict(query_filter["include"]) for x in results] +def _get_distinct_fields_as_dicts(results): + """ + Given a list of column results return a list of dictionaries where each column name is the key and the column value + is the dictionary key value + :param results: A list of sql alchemy result objects + :return: A list of dictionary representations of the sqlalchemy result objects + """ + dictionaries = [] + for result in results: + dictionary = {k: getattr(result, k) for k in result.keys()} + dictionaries.append(dictionary) + return dictionaries + + def get_first_filtered_row(table, filters): """ returns the first row that matches a given filter, in a given table From 12adcab1c8b912db69eb255f4e79e9f872627484 Mon Sep 17 00:00:00 2001 From: Keiran Price Date: Wed, 11 Sep 2019 10:18:51 +0100 Subject: [PATCH 8/8] #41: Whitespace changes --- common/database_helpers.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/common/database_helpers.py b/common/database_helpers.py index f7703e05..d9645fb9 100644 --- a/common/database_helpers.py +++ b/common/database_helpers.py @@ -363,7 +363,6 @@ def get_filtered_read_query_results(filter_handler, filters, query): query.session.close() - def _get_results_with_include(filters, results): """ Given a list of entities and a list of filters, use the include filter to nest the included entities requested in @@ -390,6 +389,7 @@ def _get_distinct_fields_as_dicts(results): dictionaries.append(dictionary) return dictionaries + def get_rows_by_filter(table, filters): """ Given a list of filters supplied in json format, returns entities that match the filters from the given table @@ -402,7 +402,6 @@ def get_rows_by_filter(table, filters): return get_filtered_read_query_results(filter_handler, filters, query) - def get_first_filtered_row(table, filters): """ returns the first row that matches a given filter, in a given table @@ -516,7 +515,7 @@ class InstrumentFacilityCyclesQuery(ReadQuery): def __init__(self, instrument_id): super().__init__(FACILITYCYCLE) investigationInstrument = aliased(INSTRUMENT) - self.base_query = self.base_query\ + self.base_query = self.base_query \ .join(FACILITYCYCLE.FACILITY) \ .join(FACILITY.INSTRUMENT) \ .join(FACILITY.INVESTIGATION) \ @@ -555,7 +554,7 @@ class InstrumentFacilityCycleInvestigationsQuery(ReadQuery): def __init__(self, instrument_id, facility_cycle_id): super().__init__(INVESTIGATION) investigationInstrument = aliased(INSTRUMENT) - self.base_query = self.base_query\ + self.base_query = self.base_query \ .join(INVESTIGATION.FACILITY) \ .join(FACILITY.FACILITYCYCLE) \ .join(FACILITY.INSTRUMENT) \