From fe9623b9f618a43ea6772f5094190209916ced2c Mon Sep 17 00:00:00 2001 From: Matthew Richards Date: Wed, 21 Oct 2020 11:15:56 +0000 Subject: [PATCH 01/10] #166: Fix Swagger data parsing for entity by ID endpoints --- src/resources/entities/entity_endpoint.py | 6 +++--- src/resources/table_endpoints/table_endpoints.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/resources/entities/entity_endpoint.py b/src/resources/entities/entity_endpoint.py index 0addd080..26202bbb 100644 --- a/src/resources/entities/entity_endpoint.py +++ b/src/resources/entities/entity_endpoint.py @@ -181,7 +181,7 @@ def get(self, id_): parameters: - in: path required: true - name: id + name: id_ description: The id of the entity to retrieve schema: type: integer @@ -215,7 +215,7 @@ def delete(self, id_): parameters: - in: path required: true - name: id + name: id_ description: The id of the entity to delete schema: type: integer @@ -246,7 +246,7 @@ def patch(self, id_): parameters: - in: path required: true - name: id + name: id_ description: The id of the entity to update schema: type: integer diff --git a/src/resources/table_endpoints/table_endpoints.py b/src/resources/table_endpoints/table_endpoints.py index d2584c89..aed0e1e7 100644 --- a/src/resources/table_endpoints/table_endpoints.py +++ b/src/resources/table_endpoints/table_endpoints.py @@ -24,7 +24,7 @@ def get(self, id_): parameters: - in: path required: true - name: id + name: id_ description: The id of the instrument to retrieve the facility cycles of schema: type: integer @@ -71,7 +71,7 @@ def get(self, id_): parameters: - in: path required: true - name: id + name: id_ description: The id of the instrument to count the facility cycles of schema: type: integer From 7d0205c8e3ea8c4d5715e972a852234222e3ec02 Mon Sep 17 00:00:00 2001 From: Matthew Richards Date: Wed, 21 Oct 2020 11:26:33 +0000 Subject: [PATCH 02/10] #166: Adapt example filter inputs for ICAT backend --- src/swagger/initialise_spec.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/swagger/initialise_spec.py b/src/swagger/initialise_spec.py index 5147b3ff..17347d49 100644 --- a/src/swagger/initialise_spec.py +++ b/src/swagger/initialise_spec.py @@ -101,11 +101,11 @@ def initialise_spec(spec): }, }, "examples": { - "eq": {"value": [{"ID": {"eq": 1}}]}, - "like": {"value": [{"NAME": {"like": "dog"}}]}, - "gte": {"value": [{"ID": {"gte": 50}}]}, - "lte": {"value": [{"ID": {"lte": 50}}]}, - "in": {"value": [{"ID": {"in": [1, 2, 3]}}]}, + "eq": {"value": {"id": {"eq": 1}}}, + "like": {"value": [{"name": {"like": "dog"}}]}, + "gte": {"value": [{"id": {"gte": 50}}]}, + "lte": {"value": [{"id": {"lte": 50}}]}, + "in": {"value": [{"id": {"in": [1, 2, 3]}}]}, }, }, ) @@ -118,7 +118,7 @@ def initialise_spec(spec): "name": "order", "description": "Apply order filters to the query. Given a field and direction, order the returned entities.", "schema": {"type": "array", "items": {"type": "string"}}, - "examples": {"asc": {"value": ["ID asc"]}, "desc": {"value": ["ID desc"]}}, + "examples": {"asc": {"value": ["id asc"]}, "desc": {"value": ["id desc"]}}, }, ) From 6666f292d1a412d244f809b8495e4f8bde98632d Mon Sep 17 00:00:00 2001 From: Matthew Richards Date: Wed, 21 Oct 2020 11:41:06 +0000 Subject: [PATCH 03/10] #166: Add not equal to DB where filter - This will match the functionality seen in the Python ICAT version of the WHERE filter, so I can put an example of this operation in the Swagger docs --- common/database/filters.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/database/filters.py b/common/database/filters.py index 8eba4e36..a6a33d08 100644 --- a/common/database/filters.py +++ b/common/database/filters.py @@ -71,6 +71,8 @@ def apply_filter(self, query): if self.operation == "eq": query.base_query = query.base_query.filter(field == self.value) + elif self.operation == "ne": + query.base_query = query.base_query.filter(field != self.value) elif self.operation == "like": query.base_query = query.base_query.filter(field.like(f"%{self.value}%")) elif self.operation == "lt": From 27249111d892468e9b659e7a76e780e0bbd4e1c0 Mon Sep 17 00:00:00 2001 From: Matthew Richards Date: Wed, 21 Oct 2020 11:59:19 +0000 Subject: [PATCH 04/10] #166: Add examples to match all possible WHERE filter operations - Also modified description to aid users if they're having issues getting example values to work --- src/swagger/initialise_spec.py | 55 +++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/src/swagger/initialise_spec.py b/src/swagger/initialise_spec.py index 17347d49..56af459f 100644 --- a/src/swagger/initialise_spec.py +++ b/src/swagger/initialise_spec.py @@ -25,7 +25,7 @@ def initialise_spec(spec): { "in": "query", "name": "where", - "description": "Apply where filters to the query. The possible operators are like, gte, lte, in and eq", + "description": "Apply where filters to the query. The possible operators are like, gte, lte, in and eq. Please modify the examples before executing a request if you are having issues with the example values.", "schema": { "type": "array", "items": { @@ -53,9 +53,28 @@ def initialise_spec(spec): }, { "type": "object", - "title": "Greater than or equal", + "title": "Inequality", "properties": { - "gte": { + "ne": { + "oneOf": [ + {"type": "string"}, + {"type": "number"}, + {"type": "integer"}, + {"type": "boolean"}, + ] + } + }, + }, + { + "type": "object", + "title": "Substring equality", + "properties": {"like": {"type": "string"}}, + }, + { + "type": "object", + "title": "Less than", + "properties": { + "lt": { "oneOf": [ {"type": "number"}, {"type": "integer"}, @@ -77,8 +96,27 @@ def initialise_spec(spec): }, { "type": "object", - "title": "Substring equality", - "properties": {"like": {"type": "string"}}, + "title": "Greater than", + "properties": { + "gt": { + "oneOf": [ + {"type": "number"}, + {"type": "integer"}, + ] + } + }, + }, + { + "type": "object", + "title": "Greater than or equal", + "properties": { + "gte": { + "oneOf": [ + {"type": "number"}, + {"type": "integer"}, + ] + } + }, }, { "type": "object", @@ -101,10 +139,13 @@ def initialise_spec(spec): }, }, "examples": { - "eq": {"value": {"id": {"eq": 1}}}, + "eq": {"value": [{"id": {"eq": 1}}]}, + "ne": {"value": [{"id": {"ne": 1}}]}, "like": {"value": [{"name": {"like": "dog"}}]}, - "gte": {"value": [{"id": {"gte": 50}}]}, + "lt": {"value": [{"id": {"lt": 10}}]}, "lte": {"value": [{"id": {"lte": 50}}]}, + "gt": {"value": [{"id": {"gt": 10}}]}, + "gte": {"value": [{"id": {"gte": 50}}]}, "in": {"value": [{"id": {"in": [1, 2, 3]}}]}, }, }, From 588158c3e20de986baaa74a4a2141175ca7a2209 Mon Sep 17 00:00:00 2001 From: Matthew Richards Date: Wed, 21 Oct 2020 12:00:15 +0000 Subject: [PATCH 05/10] #166: Add modified OpenAPI YAML from recent modifications --- src/swagger/openapi.yaml | 294 ++++++++++++++++++++++----------------- 1 file changed, 165 insertions(+), 129 deletions(-) diff --git a/src/swagger/openapi.yaml b/src/swagger/openapi.yaml index db6fbe18..0146a91f 100644 --- a/src/swagger/openapi.yaml +++ b/src/swagger/openapi.yaml @@ -69,10 +69,10 @@ components: examples: asc: value: - - ID asc + - id asc desc: value: - - ID desc + - id desc in: query name: order schema: @@ -88,31 +88,44 @@ components: type: integer WHERE_FILTER: description: Apply where filters to the query. The possible operators are like, - gte, lte, in and eq + gte, lte, in and eq. Please modify the examples before executing a request + if you are having issues with the example values. examples: eq: value: - - ID: + - id: eq: 1 + gt: + value: + - id: + gt: 10 gte: value: - - ID: + - id: gte: 50 in: value: - - ID: + - id: in: - 1 - 2 - 3 like: value: - - NAME: + - name: like: dog + lt: + value: + - id: + lt: 10 lte: value: - - ID: + - id: lte: 50 + ne: + value: + - id: + ne: 1 in: query name: where schema: @@ -132,11 +145,25 @@ components: title: Equality type: object - properties: - gte: + ne: oneOf: + - type: string - type: number - type: integer - title: Greater than or equal + - type: boolean + title: Inequality + type: object + - properties: + like: + type: string + title: Substring equality + type: object + - properties: + lt: + oneOf: + - type: number + - type: integer + title: Less than type: object - properties: lte: @@ -146,9 +173,18 @@ components: title: Less than or equal type: object - properties: - like: - type: string - title: Substring equality + gt: + oneOf: + - type: number + - type: integer + title: Greater than + type: object + - properties: + gte: + oneOf: + - type: number + - type: integer + title: Greater than or equal type: object - properties: in: @@ -1871,7 +1907,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -1895,7 +1931,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -1924,7 +1960,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -2122,7 +2158,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -2146,7 +2182,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -2175,7 +2211,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -2375,7 +2411,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -2399,7 +2435,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -2428,7 +2464,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -2629,7 +2665,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -2653,7 +2689,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -2682,7 +2718,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -2882,7 +2918,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -2906,7 +2942,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -2935,7 +2971,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -3133,7 +3169,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -3157,7 +3193,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -3186,7 +3222,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -3384,7 +3420,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -3408,7 +3444,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -3437,7 +3473,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -3636,7 +3672,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -3660,7 +3696,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -3689,7 +3725,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -3887,7 +3923,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -3911,7 +3947,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -3940,7 +3976,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -4139,7 +4175,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -4163,7 +4199,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -4192,7 +4228,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -4390,7 +4426,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -4414,7 +4450,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -4443,7 +4479,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -4641,7 +4677,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -4665,7 +4701,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -4694,7 +4730,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -4892,7 +4928,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -4916,7 +4952,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -4945,7 +4981,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -5143,7 +5179,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -5167,7 +5203,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -5196,7 +5232,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -5394,7 +5430,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -5418,7 +5454,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -5447,7 +5483,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -5646,7 +5682,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -5670,7 +5706,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -5699,7 +5735,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -5897,7 +5933,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -5921,7 +5957,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -5950,7 +5986,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -6150,7 +6186,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -6174,7 +6210,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -6203,7 +6239,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -6403,7 +6439,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -6427,7 +6463,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -6456,7 +6492,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -6656,7 +6692,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -6680,7 +6716,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -6709,7 +6745,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -6908,7 +6944,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -6932,7 +6968,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -6961,7 +6997,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -7160,7 +7196,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -7184,7 +7220,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -7213,7 +7249,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -7410,7 +7446,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -7434,7 +7470,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -7463,7 +7499,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -7661,7 +7697,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -7685,7 +7721,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -7714,7 +7750,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -7912,7 +7948,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -7936,7 +7972,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -7965,7 +8001,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -8163,7 +8199,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -8187,7 +8223,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -8216,7 +8252,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -8416,7 +8452,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -8440,7 +8476,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -8469,7 +8505,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -8667,7 +8703,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -8691,7 +8727,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -8720,7 +8756,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -8918,7 +8954,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -8942,7 +8978,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -8971,7 +9007,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -9169,7 +9205,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -9193,7 +9229,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -9222,7 +9258,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -9420,7 +9456,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -9444,7 +9480,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -9473,7 +9509,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -9672,7 +9708,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -9696,7 +9732,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -9725,7 +9761,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -9922,7 +9958,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -9946,7 +9982,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -9975,7 +10011,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -10172,7 +10208,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -10196,7 +10232,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -10225,7 +10261,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -10422,7 +10458,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -10446,7 +10482,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -10475,7 +10511,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -10673,7 +10709,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -10697,7 +10733,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -10726,7 +10762,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -10925,7 +10961,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -10949,7 +10985,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -10978,7 +11014,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -11175,7 +11211,7 @@ paths: parameters: - description: The id of the entity to delete in: path - name: id + name: id_ required: true schema: type: integer @@ -11199,7 +11235,7 @@ paths: parameters: - description: The id of the entity to retrieve in: path - name: id + name: id_ required: true schema: type: integer @@ -11228,7 +11264,7 @@ paths: parameters: - description: The id of the entity to update in: path - name: id + name: id_ required: true schema: type: integer @@ -11425,7 +11461,7 @@ paths: parameters: - description: The id of the instrument to retrieve the facility cycles of in: path - name: id + name: id_ required: true schema: type: integer @@ -11465,7 +11501,7 @@ paths: parameters: - description: The id of the instrument to count the facility cycles of in: path - name: id + name: id_ required: true schema: type: integer From efb732c9c5ba670e10cb7edbdf584649f4604094 Mon Sep 17 00:00:00 2001 From: Matthew Richards Date: Wed, 21 Oct 2020 13:28:28 +0000 Subject: [PATCH 06/10] #134: Make file adhere to Black's 88 character/line rule - Since the Python files in the repo adhere to this limit, it seems sensible that the README file also following this formatting rule where possible --- README.md | 93 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 600507d7..ee5b7cfb 100644 --- a/README.md +++ b/README.md @@ -17,10 +17,9 @@ ICAT API to interface with the Data Gateway - [Running Tests](#running-tests) - - ## Requirements -All requirements can be installed with `pip install -r requirements.txt`, and all development requirements can be installed with `pip install -r dev-requirements.txt` +All requirements can be installed with `pip install -r requirements.txt`, and all +development requirements can be installed with `pip install -r dev-requirements.txt` The required python libraries: - [SQLAlchemy](https://www.sqlalchemy.org/) @@ -30,17 +29,20 @@ The required python libraries: - [pip-tools](https://github.com/jazzband/pip-tools) (For generating requirements.txt) ## Setup and running the API -The database connection needs to be set up first. This is set in config.json, an example config file called `config.json.example` is provided. +The database connection needs to be set up first. This is set in config.json, an example +config file called `config.json.example` is provided. Ideally the API would be run with: `python -m src.main` However it can be run with the flask run command as shown below: -**Warning: the host, port and debug config options will not be respected when the API is run this way** +**Warning: the host, port and debug config options will not be respected when the API is +run this way** -To use `flask run`, the enviroment variable `FLASK_APP` should be set to `src/main.py`. Once this is -set the API can be run with `flask run` while inside the root directory of the project. The `flask run` command gets installed with flask. +To use `flask run`, the enviroment variable `FLASK_APP` should be set to `src/main.py`. +Once this is set the API can be run with `flask run` while inside the root directory of +the project. The `flask run` command gets installed with flask. Examples shown: Unix @@ -69,10 +71,10 @@ e.g. `http://localhost:5000/sessions` - ## Project structure -The project consists of 3 main packages: common, src and test. common contains modules shared across test and src such as the database mapping classes. -src contains the api resources and their http method definitions, and test contains tests for each endpoint. +The project consists of 3 main packages: common, src and test. common contains modules +shared across test and src such as the database mapping classes. src contains the api +resources and their http method definitions, and test contains tests for each endpoint. This is illustrated below. @@ -118,59 +120,67 @@ This is illustrated below. └── config.json ````` #### Main -`main.py` is where the flask_restful api is set up. This is where each endpoint resource class is generated and mapped -to an endpoint. +`main.py` is where the flask_restful api is set up. This is where each endpoint resource +class is generated and mapped to an endpoint. Example: - `api.add_resource(get_endpoint(entity_name, endpoints[entity_name]), f"/{entity_name.lower()}")` +```python +api.add_resource(get_endpoint(entity_name, endpoints[entity_name]), f"/{entity_name.lower()}") +``` #### Endpoints -The logic for each endpoint are within `/src/resources`. They are split into entities, non_entities and -table_endpoints. The entities package contains `entities_map` which maps entity names to their sqlalchemy -model. The `entity_endpoint` module contains the function that is used to generate endpoints at start up. -`table_endpoints` contains the endpoint classes that are table specific. Finally, non_entities contains the -session endpoint. +The logic for each endpoint are within `/src/resources`. They are split into entities, +non_entities and table_endpoints. The entities package contains `entities_map` which +maps entity names to their sqlalchemy model. The `entity_endpoint` module contains the +function that is used to generate endpoints at start up. `table_endpoints` contains the +endpoint classes that are table specific. Finally, non_entities contains the session +endpoint. #### Mapped classes -The classes mapped from the database are stored in `/common/database/models.py`. Each model was -automatically generated using sqlacodegen. A class `EntityHelper` is defined so that each model may -inherit two methods `to_dict()` and `update_from_dict(dictionary)`, both used for returning entities -and updating them, in a form easily converted to JSON. - - +The classes mapped from the database are stored in `/common/database/models.py`. Each +model was automatically generated using sqlacodegen. A class `EntityHelper` is defined +so that each model may inherit two methods `to_dict()` and +`update_from_dict(dictionary)`, both used for returning entities and updating them, in a +form easily converted to JSON. ## Database Generator -There is a tool to generate mock data into the database. It is located in `util/icat_db_generator.py` -By default it will generate 20 years worth of data (approx 70,000 entities). The script makes use of -`random` and `Faker` and is seeded with a seed of 1. The seed and number of years of data generated can -be changed by using the arg flags `-s` or `--seed` for the seed, and `-y` or `--years` for the number of years. -For example: -`python -m util.icat_db_generator -s 4 -y 10` Would set the seed to 4 and generate 10 years of data. +There is a tool to generate mock data into the database. It is located in +`util/icat_db_generator.py`. By default it will generate 20 years worth of data (approx +70,000 entities). The script makes use of `random` and `Faker` and is seeded with a seed +of 1. The seed and number of years of data generated can be changed by using the arg +flags `-s` or `--seed` for the seed, and `-y` or `--years` for the number of years. For +example: `python -m util.icat_db_generator -s 4 -y 10` Would set the seed to 4 and +generate 10 years of data. #### Querying and filtering: -The querying and filtering logic is located in `/common/database_helpers.py`. In this module the abstract `Query` and -`QueryFilter` classes are defined as well as their implementations. The functions that are used by various endpoints to -query the database are also in this module. -Class diagrams for this module: +The querying and filtering logic is located in `/common/database_helpers.py`. In this +module the abstract `Query` and `QueryFilter` classes are defined as well as their +implementations. The functions that are used by various endpoints to query the database +are also in this module. + + +#### Class diagrams for this module: ![image](https://user-images.githubusercontent.com/44777678/67954353-ba69ef80-fbe8-11e9-81e3-0668cea3fa35.png) ![image](https://user-images.githubusercontent.com/44777678/67954834-7fb48700-fbe9-11e9-96f3-ffefc7277ebd.png) #### Authentication -Each request requires a valid session ID to be provided in the Authorization header. This header should take the form of `{"Authorization":"Bearer "}` A session ID can be obtained by -sending a post request to `/sessions/` -All endpoint methods that require a session id are decorated with `@requires_session_id` +Each request requires a valid session ID to be provided in the Authorization header. +This header should take the form of `{"Authorization":"Bearer "}` A session +ID can be obtained by sending a post request to `/sessions/`. All endpoint methods that +require a session id are decorated with `@requires_session_id` #### Generating the swagger spec: `openapi.yaml` -The swagger generation script is located in `/src/swagger/swagger_generator.py`. The script will only run when -the config option `generate_swagger` is set to true in `config.json`. The generator decorates the first endpoint -resource class in it's module to get the name of the entity. It then creates the correct paths using the name of the +The swagger generation script is located in `/src/swagger/swagger_generator.py`. The +script will only run when the config option `generate_swagger` is set to true in +`config.json`. The generator decorates the first endpoint resource class in it's module +to get the name of the entity. It then creates the correct paths using the name of the entity and outputs the swagger spec to `openapi.yaml` Example of the decorator: @@ -191,6 +201,7 @@ To run the tests use `python -m unittest discover` When writing code for this repository, [Black](https://black.readthedocs.io/en/stable/) is used as the code linter/formatter to ensure the code is kept Pythonic. Installing the dev requirements will ensure this package is installed. This repository uses the -default settings for Black; to use, execute the following command on the root directory of this repo: +default settings for Black; to use, execute the following command on the root directory +of this repo: `black .` From c2e8099f00861e3c61cf945415091ecc03131c3b Mon Sep 17 00:00:00 2001 From: Matthew Richards Date: Wed, 21 Oct 2020 15:45:08 +0000 Subject: [PATCH 07/10] #134: Edit Swagger doc section of README to reflect changes in generation --- README.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index ee5b7cfb..d67fceec 100644 --- a/README.md +++ b/README.md @@ -175,23 +175,22 @@ ID can be obtained by sending a post request to `/sessions/`. All endpoint metho require a session id are decorated with `@requires_session_id` - #### Generating the swagger spec: `openapi.yaml` -The swagger generation script is located in `/src/swagger/swagger_generator.py`. The -script will only run when the config option `generate_swagger` is set to true in -`config.json`. The generator decorates the first endpoint resource class in it's module -to get the name of the entity. It then creates the correct paths using the name of the -entity and outputs the swagger spec to `openapi.yaml` - -Example of the decorator: -```python -@swagger_gen.resource_wrapper() -class DataCollectionDatasets(Resource): - @requires_session_id - @queries_records - def get(self): - return get_rows_by_filter(DATACOLLECTIONDATASET, get_filters_from_query_string()), 200 -``` +When the config option `generate_swagger` is set to true in `config.json`, a YAML +file defining the API using OpenAPI standards will be created at +`src/swagger/openapi.yaml`. [apispec](https://apispec.readthedocs.io/en/latest/) is used +to help with this, with an `APISpec()` object created in `src/main.py` which is added to +(using `APISpec.path()`) when the endpoints are created for Flask. These paths are +iterated over and ordered alphabetically, to ensure `openapi.yaml` only changes if there +have been changes to the Swagger docs of the API; without that code, Git will detect +changes on that file everytime startup occurs (preventing a clean development repo). The +contents of the `APISpec` object are written to a YAML file and is used when the user +goes to the configured (root) page in their browser. + +The endpoint related files in `src/resources/` contain `__doc__` which have the Swagger +docs for each type of endpoint. `src/resources/swagger/` contain code to aid Swagger doc +generation, with a plugin (`RestfulPlugin`) created for `apispec` to extract Swagger +documentation from `flask-restful` functions. ## Running Tests From da21adb735c842b72da6bcb4e6806e286f974786 Mon Sep 17 00:00:00 2001 From: Matthew Richards Date: Wed, 21 Oct 2020 15:49:14 +0000 Subject: [PATCH 08/10] #134: Misc. changes of existing sections in README - Also updated `requirements.in` (with a freshly compiled `requirements.txt`) to reflect the requirement of Python ICAT --- README.md | 12 +++++++++--- requirements.in | 2 +- requirements.txt | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d67fceec..da152ef1 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ ICAT API to interface with the Data Gateway - [Main](#main) - [Endpoints](#endpoints) - [Mapped classes](#mapped-classes) + - [Database Generator](#database-generator) + - [Class Diagrams](#class-diagrams-for-this-module) - [Querying and filtering](#querying-and-filtering) - [Swagger Generation](#generating-the-swagger-spec-openapiyaml) - [Authentication](#authentication) @@ -25,8 +27,12 @@ The required python libraries: - [SQLAlchemy](https://www.sqlalchemy.org/) - [flask-restful](https://github.com/flask-restful/flask-restful/) - [pymysql](https://pymysql.readthedocs.io/en/latest/) - - [pyyaml](https://pyyaml.org/wiki/PyYAMLDocumentation) (For the swagger generation) - - [pip-tools](https://github.com/jazzband/pip-tools) (For generating requirements.txt) + - [apispec](https://apispec.readthedocs.io/en/latest/) (For the swagger generation) + - [pip-tools](https://github.com/jazzband/pip-tools) (For generating + requirements.txt) + - [python-icat](https://python-icat.readthedocs.io/en/stable) (For ICAT backend) + + ## Setup and running the API The database connection needs to be set up first. This is set in config.json, an example @@ -196,7 +202,7 @@ documentation from `flask-restful` functions. ## Running Tests To run the tests use `python -m unittest discover` -## Linter +## Code Formatter When writing code for this repository, [Black](https://black.readthedocs.io/en/stable/) is used as the code linter/formatter to ensure the code is kept Pythonic. Installing the dev requirements will ensure this package is installed. This repository uses the diff --git a/requirements.in b/requirements.in index 8b8d42d8..1db77744 100644 --- a/requirements.in +++ b/requirements.in @@ -4,4 +4,4 @@ pymysql == 0.9.3 flask-cors == 3.0.8 apispec == 3.3.0 flask-swagger-ui == 3.25.0 -pyyaml == 5.1.2 \ No newline at end of file +python-icat == 0.17.0 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 752e3265..f6055a6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,8 +15,8 @@ itsdangerous==1.1.0 # via flask jinja2==2.10.1 # via flask markupsafe==1.1.1 # via jinja2 pymysql==0.9.3 # via -r requirements.in +python-icat==0.17.0 # via -r requirements.in pytz==2019.2 # via flask-restful -pyyaml==5.1.2 # via -r requirements.in six==1.12.0 # via flask-cors, flask-restful sqlalchemy==1.3.8 # via -r requirements.in werkzeug==0.16.0 # via flask From 80943adebfdb2ffa3ae8677d1f6d327197e5f136 Mon Sep 17 00:00:00 2001 From: Matthew Richards Date: Thu, 22 Oct 2020 17:58:49 +0000 Subject: [PATCH 09/10] #147: Misc. code formatting changes --- common/icat/helpers.py | 8 ++------ common/icat/query.py | 13 ++----------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/common/icat/helpers.py b/common/icat/helpers.py index d1f8440a..85d9cc7b 100644 --- a/common/icat/helpers.py +++ b/common/icat/helpers.py @@ -487,9 +487,7 @@ def get_facility_cycles_for_instrument( log.info("Getting a list of facility cycles from the specified instrument for ISIS") query_aggregate = "COUNT:DISTINCT" if count_query else "DISTINCT" - query = ICATQuery( - client, "FacilityCycle", aggregate=query_aggregate - ) + query = ICATQuery(client, "FacilityCycle", aggregate=query_aggregate) instrument_id_check = PythonICATWhereFilter( "facility.instruments.id", instrument_id, "eq" @@ -572,9 +570,7 @@ def get_investigations_for_instrument_in_facility_cycle( ) query_aggregate = "COUNT:DISTINCT" if count_query else "DISTINCT" - query = ICATQuery( - client, "Investigation", aggregate=query_aggregate - ) + query = ICATQuery(client, "Investigation", aggregate=query_aggregate) instrument_id_check = PythonICATWhereFilter( "facility.instruments.id", instrument_id, "eq" diff --git a/common/icat/query.py b/common/icat/query.py index 99fa2d03..1808167f 100644 --- a/common/icat/query.py +++ b/common/icat/query.py @@ -15,12 +15,7 @@ class ICATQuery: def __init__( - self, - client, - entity_name, - conditions=None, - aggregate=None, - includes=None, + self, client, entity_name, conditions=None, aggregate=None, includes=None, ): """ Create a Query object within Python ICAT @@ -58,7 +53,6 @@ def __init__( " suggesting an invalid argument" ) - def execute_query(self, client, return_json_formattable=False): """ Execute the ICAT Query object and return in the format specified by the @@ -93,10 +87,7 @@ def execute_query(self, client, return_json_formattable=False): count_query = True log.debug("This ICATQuery is used for COUNT purposes") - if ( - self.query.aggregate == "DISTINCT" - and not count_query - ): + if self.query.aggregate == "DISTINCT" and not count_query: log.info("Extracting the distinct fields from query's conditions") # Check query's conditions for the ones created by the distinct filter distinct_attributes = self.iterate_query_conditions_for_distinctiveness() From 511a8ef6c26625dd7f9289a5a101962b9a4b3b3f Mon Sep 17 00:00:00 2001 From: Matthew Richards Date: Fri, 23 Oct 2020 15:37:21 +0000 Subject: [PATCH 10/10] #147: Override handle_error on extended Api class --- src/main.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main.py b/src/main.py index f93805f8..819d7080 100644 --- a/src/main.py +++ b/src/main.py @@ -35,18 +35,21 @@ security=[{"session_id": []}], ) -app = Flask(__name__) -cors = CORS(app) -app.url_map.strict_slashes = False -api = Api(app) - -def handle_error(e): - return str(e), e.status_code +class CustomErrorHandledApi(Api): + """ + This class overrides `handle_error` function from the API class from `flask_restful` + to correctly return response codes and exception messages from uncaught exceptions + """ + def handle_error(self, e): + return str(e), e.status_code -app.register_error_handler(ApiError, handle_error) +app = Flask(__name__) +cors = CORS(app) +app.url_map.strict_slashes = False +api = CustomErrorHandledApi(app) swaggerui_blueprint = get_swaggerui_blueprint( "", "/openapi.json", config={"app_name": "DataGateway API OpenAPI Spec"},