-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
229 lines (194 loc) · 8 KB
/
main.py
File metadata and controls
229 lines (194 loc) · 8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
###
# Copyright 2025, ISB
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
###
import os
from flask import Flask, render_template, request, redirect, url_for, jsonify
import requests
from google.cloud import bigquery
from google.cloud.bigquery import QueryJobConfig
import logging
from google.api_core.exceptions import BadRequest
import bq_builder
import settings
import swagger_config
import concurrent.futures
import json
from flasgger import Swagger, swag_from
# If Default App Credentials isn't working, make a JSON key for the local development service account and load it
# manually by uncommenting these two lines:
#if not settings.IS_APP_ENGINE:
# os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = os.path.join(os.environ['SECURE_PATH'], 'privatekey.json')
logger = logging.getLogger(__name__)
app = Flask(__name__)
if not settings.IS_APP_ENGINE:
from flask.logging import default_handler
logger.addHandler(default_handler)
# landing page
@app.route("/", methods=['POST', 'GET'])
def home():
return redirect(url_for('search', status='current', include_always_newest='false'))
# about page
@app.route("/about/")
def about():
return render_template("about.html")
# privacy policy page
@app.route("/privacy/")
def privacy():
return render_template("privacy.html")
# search page
@app.route("/search", methods=['POST', 'GET'])
def search(status=None):
error_msg = settings.pull_metadata()
if error_msg:
app.logger.error(f"[ERROR] {error_msg}")
if request.method == 'POST':
rq_meth = request.form
else:
rq_meth = request.args
selected_filters = rq_meth.to_dict(flat=False)
return render_template("bq_meta_search.html", bq_filters=settings.bq_table_files['bq_filters']['file_data'],
selected_filters=selected_filters,
bq_total_entries=settings.bq_total_entries)
# search api: returns search result in json format
@swag_from('api_docs/search_api_get.yaml', endpoint='search_api', methods=['GET'])
@swag_from('api_docs/search_api_post.yaml', endpoint='search_api', methods=['POST'])
@app.route("/search_api", methods=['GET', 'POST'])
def search_api():
error_msg = settings.pull_metadata()
status_code = 200
if error_msg:
app.logger.error(f"[ERROR] {error_msg}")
filtered_meta_data = []
try:
query_statement, parameters = bq_builder.metadata_query(request)
bigquery_client = bigquery.Client(project=settings.BQ_METADATA_PROJ)
# Build Query Job Config
job_config = QueryJobConfig(allow_large_results=True, use_query_cache=False, priority='INTERACTIVE')
if parameters and len(parameters):
job_config.query_parameters = parameters
job_config.use_legacy_sql = False
query_job = bigquery_client.query(query_statement, job_config=job_config)
result = query_job.result(timeout=30)
filtered_meta_data = [json.loads(dict(row)['metadata']) for row in result]
except Exception as e:
status_code=400
error_msg = "There was an error during the search."
if isinstance(e, ValueError):
error_msg = 'An invalid query parameter was detected. Please revise your search criteria and search again.'
elif isinstance(e, (concurrent.futures.TimeoutError, requests.exceptions.ReadTimeout)):
error_msg = "Sorry, query job has timed out."
status_code = 408
logger.error(f"[ERROR] {error_msg}")
logger.exception(e)
if error_msg:
response = jsonify({'message': error_msg})
else:
response = jsonify(filtered_meta_data)
response.status_code = status_code
return response
# fetches table's preview (up to 8 rows)
@swag_from('api_docs/get_tbl_preview.yaml', endpoint='get_tbl_preview', methods=['GET'])
@app.route("/get_tbl_preview/<proj_id>/<dataset_id>/<table_id>/", methods=['GET'])
def get_tbl_preview(proj_id, dataset_id, table_id):
status = 200
max_row = 8
try:
if not proj_id or not dataset_id or not table_id:
app.logger.warning("[WARNING] Required ID missing")
status = 400
result = {
'message': "One or more required parameters (project id, dataset_id or table_id) were not supplied."
}
else:
client = bigquery.Client(project=proj_id)
rows_iter = client.list_rows(f'{proj_id}.{dataset_id}.{table_id}', max_results=max_row)
if rows_iter and rows_iter.total_rows:
schema_fields = [schema.to_api_repr() for schema in list(rows_iter.schema)]
tbl_data = [dict(row) for row in list(rows_iter)]
result = {
'tbl_data': tbl_data,
'schema_fields': schema_fields
}
else:
status = 204
result = {
'message': f'No record has been found for table {proj_id}.{dataset_id}.{table_id}.'
}
except Exception as e:
logger.error(f"[ERROR] {e}")
logger.exception(e)
status = 500
err_type = "Error"
if isinstance(e, ValueError):
status = 400
err_type = "ValueError"
result = {
'message': err_type
}
if status != 200:
logger.error(
f"[ERROR] While attempting to retrieve preview data for {proj_id}.{dataset_id}.{table_id} table: [{status}] {result['message']}")
response = jsonify(result)
response.status_code = status
return response
# get the dropdown options or checkbox options for the filter type
@swag_from('api_docs/get_filter_options.yaml', endpoint='get_filter_options', methods=['GET'])
@app.route("/get_filter_options/<filter_type>/", methods=['GET'])
def get_filter_options(filter_type):
status = 200
filter_type_options = ['category', 'status', 'program', 'data_type', 'experimental_strategy', 'reference_genome',
'source', 'project_id']
filter_options = None
try:
settings.pull_metadata()
filter_dic = settings.bq_table_files['bq_filters']
if filter_dic:
file_data = filter_dic['file_data']
if filter_type:
if filter_type in file_data:
file_data = file_data[filter_type]
elif filter_type not in filter_type_options:
raise ValueError(f"Invalid filter_type - '{filter_type}' is given")
else:
raise ValueError(f"No filter_type value was given. filter_type value can be one of the followings: [{', '.joins(filter_type_options)}]")
options = []
for op in file_data['options']:
options.append(op['value'])
filter_options = {
"options": options
}
response_obj = filter_options
except Exception as e:
logger.error(f"[ERROR] {e}")
logger.exception(e)
status = 500
err_type = "Error"
if isinstance(e, ValueError):
status = 400
err_type = "ValueError"
response_obj = {'message': f"{status} {err_type}: {e}"}
response = jsonify(response_obj)
response.status_code = status
return response
# handle 404 error
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
# setup application
settings.setup_app(app)
# initialize Swagger
swagger = Swagger(app, template=swagger_config.swagger_template,config=swagger_config.swagger_config)
if __name__ == '__main__':
app.run(host='127.0.0.1', port=8080, debug=True)