Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adql query parser function #696

Draft
wants to merge 48 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
5f710cd
adql query parser function
burnout87 Jun 24, 2024
5526e61
queryparser-python3 lib
burnout87 Jun 25, 2024
7d099aa
more error check
burnout87 Jun 25, 2024
fb87a08
adding info
burnout87 Jul 8, 2024
a90cc05
using sqlparse library
burnout87 Jul 18, 2024
ba08496
requirements
burnout87 Jul 18, 2024
6dcc1e2
using the two libraries combined
burnout87 Jul 18, 2024
eec215c
some where clauses extraction
burnout87 Jul 23, 2024
6759c7a
testing breadth-first
burnout87 Jul 26, 2024
0e24a34
Merge branch 'master' into ivoa_helper-endpoint
burnout87 Jul 29, 2024
90c8e4f
using breadth-first
burnout87 Jul 29, 2024
2c0030b
no sqlparse
burnout87 Jul 30, 2024
16580c2
querying mysql gallery database
burnout87 Jul 30, 2024
30d1ed8
todo and removed commented lines
burnout87 Jul 31, 2024
27708b2
vo options in the dispatcher config
burnout87 Jul 31, 2024
83d119a
extracting mysql parameters from config
burnout87 Jul 31, 2024
89f46d6
no need for breadth first search
burnout87 Jul 31, 2024
b828910
sentry in case of error
burnout87 Jul 31, 2024
34bdea8
capturing general exception
burnout87 Jul 31, 2024
1ee31a1
dispatcher endpoint
burnout87 Jul 31, 2024
40dc17e
build product gallery path and jsonify the response
burnout87 Jul 31, 2024
60c9bff
var renaming
burnout87 Aug 12, 2024
bdc61b4
removed unused imports
burnout87 Aug 16, 2024
3d8d973
Merge branch 'master' into ivoa_helper-endpoint
burnout87 Aug 16, 2024
841928f
Merge branch 'master' into ivoa_helper-endpoint
burnout87 Aug 16, 2024
b31c634
Merge branch 'master' into ivoa_helper-endpoint
burnout87 Sep 25, 2024
5342a7a
using postgresql
burnout87 Sep 26, 2024
bf1cd49
sanitize request values
burnout87 Sep 26, 2024
5d53425
using value var
burnout87 Sep 27, 2024
309e2ad
postgresql connector library
burnout87 Sep 27, 2024
08ad2ab
no query parsing
burnout87 Sep 27, 2024
ec8d018
not needed import
burnout87 Sep 27, 2024
f5df283
adapted conf example
burnout87 Sep 27, 2024
f9b934b
not needed requirements
burnout87 Sep 27, 2024
87256a9
not needed requirements
burnout87 Sep 27, 2024
db388d8
freezing version pytest-xdist
burnout87 Sep 27, 2024
d8d26a7
adapted config tests and new config test
burnout87 Sep 27, 2024
c81decd
Merge branch 'master' into ivoa_helper-endpoint
burnout87 Oct 4, 2024
0dd8cb5
exception handling
burnout87 Oct 4, 2024
2c1baf4
logging
burnout87 Oct 4, 2024
73f9ede
fixing imports
burnout87 Oct 4, 2024
ebf7b24
missing fixture decorator
burnout87 Oct 4, 2024
f069dad
Merge branch 'master' into ivoa_helper-endpoint
burnout87 Oct 15, 2024
ddbaadc
build file url
burnout87 Oct 17, 2024
77ad5f3
removed mysql stuff
burnout87 Oct 23, 2024
b26a5da
removed pgsql details from answer
burnout87 Oct 23, 2024
516c5c9
Merge branch 'master' into ivoa_helper-endpoint
burnout87 Dec 6, 2024
0d31fb9
Merge branch 'master' into ivoa_helper-endpoint
burnout87 Dec 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions cdci_data_analysis/analysis/ivoa_helper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import os.path

from queryparser.adql import ADQLQueryTranslator
from queryparser.exceptions import QuerySyntaxError

from psycopg2 import connect, DatabaseError

from ..app_logging import app_logging
from ..analysis.exceptions import RequestNotUnderstood

logger = app_logging.getLogger('ivoa_helper')


def parse_adql_query(query):
try:
adt = ADQLQueryTranslator(query)

Check warning on line 16 in cdci_data_analysis/analysis/ivoa_helper.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L15-L16

Added lines #L15 - L16 were not covered by tests

output_obj = dict(

Check warning on line 18 in cdci_data_analysis/analysis/ivoa_helper.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L18

Added line #L18 was not covered by tests
mysql_query=None,
psql_query=adt.to_postgresql()
)

except QuerySyntaxError as qe:
logger.error(f'Error while parsing the ADQL query: {str(qe)}')
raise RequestNotUnderstood(f"Error while parsing the ADQL query: {str(qe)}")
return output_obj

Check warning on line 26 in cdci_data_analysis/analysis/ivoa_helper.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L23-L26

Added lines #L23 - L26 were not covered by tests


def run_ivoa_query(query, **kwargs):
parsed_query_obj = parse_adql_query(query)

Check warning on line 30 in cdci_data_analysis/analysis/ivoa_helper.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L30

Added line #L30 was not covered by tests

logger.info('Performing query on the product_gallery')
vo_psql_pg_host = kwargs.get('vo_psql_pg_host', None)
vo_psql_pg_user = kwargs.get('vo_psql_pg_user', None)
vo_psql_pg_password = kwargs.get('vo_psql_pg_password', None)
vo_psql_pg_db = kwargs.get('vo_psql_pg_db', None)
product_gallery_url = kwargs.get('product_gallery_url', None)
result_list = run_ivoa_query_from_product_gallery(parsed_query_obj,

Check warning on line 38 in cdci_data_analysis/analysis/ivoa_helper.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L32-L38

Added lines #L32 - L38 were not covered by tests
vo_psql_pg_host=vo_psql_pg_host,
vo_psql_pg_user=vo_psql_pg_user,
vo_psql_pg_password=vo_psql_pg_password,
vo_psql_pg_db=vo_psql_pg_db,
product_gallery_url=product_gallery_url)
return result_list

Check warning on line 44 in cdci_data_analysis/analysis/ivoa_helper.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L44

Added line #L44 was not covered by tests


def run_ivoa_query_from_product_gallery(parsed_query_obj,
vo_psql_pg_host,
vo_psql_pg_user,
vo_psql_pg_password,
vo_psql_pg_db,
product_gallery_url=None
):
result_list = []
connection = None
try:
with connect(

Check warning on line 57 in cdci_data_analysis/analysis/ivoa_helper.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L54-L57

Added lines #L54 - L57 were not covered by tests
host=vo_psql_pg_host,
database=vo_psql_pg_db,
user=vo_psql_pg_user,
password=vo_psql_pg_password
) as connection:
db_query = parsed_query_obj.get('psql_query')
with connection.cursor() as cursor:
cursor.execute(db_query)
for row in cursor:
list_row = list(row)
if product_gallery_url is not None:
for index, value in enumerate(list_row):
description = cursor.description[index]
if description.name in {'file_uri', 'file_name'} and value is not None and isinstance(value, str):
file_name_list = [v.strip() for v in value.split(',')]
if description.name == 'file_uri':
for id, file_name in enumerate(file_name_list):
file_name_list[id] = os.path.join(product_gallery_url, 'sites/default/files/', file_name.strip())
list_row[index] = file_name_list
if description.name in {'path', 'path_alias'} and value is not None and isinstance(value, str):
if value.startswith('/'):
value = value[1:]
list_row[index] = os.path.join(product_gallery_url, value)
result_list.append(list_row)

Check warning on line 81 in cdci_data_analysis/analysis/ivoa_helper.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L63-L81

Added lines #L63 - L81 were not covered by tests

except (Exception, DatabaseError) as e:
logger.error(f"Error when querying to the Postgresql server: {str(e)}")
raise e

Check warning on line 85 in cdci_data_analysis/analysis/ivoa_helper.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L83-L85

Added lines #L83 - L85 were not covered by tests

finally:
if connection is not None:
cursor.close()
connection.close()
logger.info('Database connection closed')

Check warning on line 91 in cdci_data_analysis/analysis/ivoa_helper.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L88-L91

Added lines #L88 - L91 were not covered by tests

return result_list

Check warning on line 93 in cdci_data_analysis/analysis/ivoa_helper.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L93

Added line #L93 was not covered by tests
13 changes: 12 additions & 1 deletion cdci_data_analysis/config_dir/conf_env.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,15 @@ dispatcher:
# url for the conversion of a given time, in UTC format, to the correspondent REVNUM
converttime_revnum_service_url: COVERTTIME_REVNUM_SERVICE_URL


# virtual observatory related configurations (eg mysql credentials)
vo_options:
# mysql credentials
vo_mysql_pg_host: MYSQL_PG_HOST
vo_mysql_pg_user: MYSQL_PG_USER
vo_mysql_pg_password: MYSQL_PG_PASSWORD
vo_mysql_pg_db: MYSQL_PG_DB
# postgresql credentials
vo_psql_pg_host: PSQL_PG_HOST
vo_psql_pg_user: PSQL_PG_USER
vo_psql_pg_password: PSQL_PG_PASSWORD
vo_psql_pg_db: PSQL_PG_DB
24 changes: 24 additions & 0 deletions cdci_data_analysis/configurer.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,14 @@ def __init__(self, cfg_dict, origin=None):
disp_dict.get('renku_options', {}).get('renku_gitlab_repository_url', None),
disp_dict.get('renku_options', {}).get('renku_base_project_url', None),
disp_dict.get('renku_options', {}).get('ssh_key_path', None),
disp_dict.get('vo_options', {}).get('vo_mysql_pg_host', None),
disp_dict.get('vo_options', {}).get('vo_mysql_pg_user', None),
disp_dict.get('vo_options', {}).get('vo_mysql_pg_password', None),
disp_dict.get('vo_options', {}).get('vo_mysql_pg_db', None),
disp_dict.get('vo_options', {}).get('vo_psql_pg_host', None),
disp_dict.get('vo_options', {}).get('vo_psql_pg_user', None),
disp_dict.get('vo_options', {}).get('vo_psql_pg_password', None),
disp_dict.get('vo_options', {}).get('vo_psql_pg_db', None)
)

# not used?
Expand Down Expand Up @@ -348,6 +356,14 @@ def set_conf_dispatcher(self,
renku_gitlab_repository_url,
renku_base_project_url,
renku_gitlab_ssh_key_path,
vo_mysql_pg_host,
vo_mysql_pg_user,
vo_mysql_pg_password,
vo_mysql_pg_db,
vo_psql_pg_host,
vo_psql_pg_user,
vo_psql_pg_password,
vo_psql_pg_db
):
# Generic to dispatcher
#print(dispatcher_url, dispatcher_port)
Expand Down Expand Up @@ -400,6 +416,14 @@ def set_conf_dispatcher(self,
self.renku_gitlab_repository_url = renku_gitlab_repository_url
self.renku_gitlab_ssh_key_path = renku_gitlab_ssh_key_path
self.renku_base_project_url = renku_base_project_url
self.vo_mysql_pg_host = vo_mysql_pg_host
self.vo_mysql_pg_user = vo_mysql_pg_user
self.vo_mysql_pg_password = vo_mysql_pg_password
self.vo_mysql_pg_db = vo_mysql_pg_db
self.vo_psql_pg_host = vo_psql_pg_host
self.vo_psql_pg_user = vo_psql_pg_user
self.vo_psql_pg_password = vo_psql_pg_password
self.vo_psql_pg_db = vo_psql_pg_db

def get_data_serve_conf(self, instr_name):
if instr_name in self.data_server_conf_dict.keys():
Expand Down
55 changes: 53 additions & 2 deletions cdci_data_analysis/flask_app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import time as _time
from urllib.parse import urlencode, urlparse

from cdci_data_analysis.analysis import drupal_helper, tokenHelper, renku_helper, email_helper, matrix_helper
from cdci_data_analysis.analysis import drupal_helper, tokenHelper, renku_helper, email_helper, matrix_helper, ivoa_helper
from .logstash import logstash_message
from .schemas import QueryOutJSON, dispatcher_strict_validate
from marshmallow.exceptions import ValidationError
Expand Down Expand Up @@ -269,7 +269,7 @@
'product_gallery_secret_key',
'matrix_sender_access_token', 'matrix_incident_report_sender_personal_access_token',
'matrix_bcc_receivers_room_ids', 'matrix_incident_report_receivers_room_ids',
'smtp_server_password'])
'smtp_server_password', 'vo_psql_pg_db', 'vo_psql_pg_host', 'vo_psql_pg_password', 'vo_psql_pg_user'])
}

plugins = {}
Expand Down Expand Up @@ -430,6 +430,57 @@
"Our team is notified and is working on it.")


@app.route('/run_adql_query')
def run_adql_query():
try:
par_dic = request.values.to_dict()
sanitized_request_values = sanitize_dict_before_log(par_dic)
logger.info('\033[32m===========================> run_adql_query\033[0m')

Check warning on line 438 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L435-L438

Added lines #L435 - L438 were not covered by tests

logger.info('\033[33m raw request values: %s \033[0m', dict(sanitized_request_values))

Check warning on line 440 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L440

Added line #L440 was not covered by tests

token = par_dic.get('token', None)
app_config = app.config.get('conf')
secret_key = app_config.secret_key

Check warning on line 444 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L442-L444

Added lines #L442 - L444 were not covered by tests

output, output_code = tokenHelper.validate_token_from_request(token=token, secret_key=secret_key,

Check warning on line 446 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L446

Added line #L446 was not covered by tests
required_roles=['ivoa_user'],
action="run an ADQL query")

if output_code is not None:
return make_response(output, output_code)

Check warning on line 451 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L450-L451

Added lines #L450 - L451 were not covered by tests

adql_query = par_dic.get('adql_query', None)
vo_psql_pg_host = app_config.vo_psql_pg_host
vo_psql_pg_user = app_config.vo_psql_pg_user
vo_psql_pg_password = app_config.vo_psql_pg_password
vo_psql_pg_db = app_config.vo_psql_pg_db
product_gallery_url = app_config.product_gallery_url

Check warning on line 458 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L453-L458

Added lines #L453 - L458 were not covered by tests

result_query = ivoa_helper.run_ivoa_query(adql_query,

Check warning on line 460 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L460

Added line #L460 was not covered by tests
vo_psql_pg_host=vo_psql_pg_host,
vo_psql_pg_user=vo_psql_pg_user,
vo_psql_pg_password=vo_psql_pg_password,
vo_psql_pg_db=vo_psql_pg_db,
product_gallery_url=product_gallery_url)

return jsonify(result_query)
except APIerror as api_e:
error_message = f"Error while running an ADQL query: "
if hasattr(api_e, 'message') and api_e.message is not None:
error_message += api_e.message

Check warning on line 471 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L467-L471

Added lines #L467 - L471 were not covered by tests
else:
error_message += f"{repr(api_e)}"
logging.getLogger().error(error_message)
sentry.capture_message(error_message)

Check warning on line 475 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L473-L475

Added lines #L473 - L475 were not covered by tests

return make_response(error_message)
except Exception as e:
error_message = f"Error while running an ADQL query: {str(e)}\n{traceback.format_exc()}"
logging.getLogger().error(error_message)
sentry.capture_message(error_message)
return make_response(f"Internal error while running an ADQL query. Our team is notified and is working on it.")

Check warning on line 482 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L477-L482

Added lines #L477 - L482 were not covered by tests


@app.route('/run_analysis', methods=['POST', 'GET'])
def run_analysis():
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pyyaml
simplejson
flask==2.0.3
astropy>=5.0.1
pytest-xdist<=3.5.0
pylogstash_context>=0.1.19
gunicorn
decorator
Expand All @@ -29,6 +30,7 @@ GitPython
nbformat
sentry-sdk
pytest-sentry
queryparser-python3
-e git+https://github.com/oda-hub/oda_api.git#egg=oda_api

MarkupSafe==2.0.1
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@
"black>=22.10.0",
"bs4",
"GitPython",
"queryparser-python3",
"nbformat",
"giturlparse",
"sentry-sdk",
"validators==0.28.3",
"jsonschema<=4.17.3"
"jsonschema<=4.17.3",
'psycopg2'
]

test_req = [
Expand Down
25 changes: 25 additions & 0 deletions tests/test_server_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -2458,6 +2458,7 @@ def test_example_config(dispatcher_test_conf):
example_config = yaml.load(open(example_config_fn), Loader=yaml.SafeLoader)['dispatcher']
example_config.pop('product_gallery_options', None)
example_config.pop('matrix_options', None)
example_config.pop('vo_options', None)

mapper = lambda x, y: ".".join(map(str, x))
example_config_keys = flatten_nested_structure(example_config, mapper)
Expand All @@ -2479,6 +2480,7 @@ def test_example_config_with_gallery(dispatcher_test_conf_with_gallery):

example_config = yaml.load(open(example_config_fn), Loader=yaml.SafeLoader)['dispatcher']
example_config.pop('matrix_options', None)
example_config.pop('vo_options', None)

mapper = lambda x, y: ".".join(map(str, x))
example_config_keys = flatten_nested_structure(example_config, mapper)
Expand All @@ -2500,6 +2502,7 @@ def test_example_config_with_matrix_options(dispatcher_test_conf_with_matrix_opt
with open(example_config_fn) as example_config_fn_f:
example_config = yaml.load(example_config_fn_f, Loader=yaml.SafeLoader)['dispatcher']
example_config.pop('product_gallery_options', None)
example_config.pop('vo_options', None)

mapper = lambda x, y: ".".join(map(str, x))
example_config_keys = flatten_nested_structure(example_config, mapper)
Expand All @@ -2511,6 +2514,28 @@ def test_example_config_with_matrix_options(dispatcher_test_conf_with_matrix_opt
assert set(example_config_keys) == set(test_config_keys)


def test_example_config_with_vo_options(dispatcher_test_conf_with_vo_options):
import cdci_data_analysis.config_dir

example_config_fn = os.path.join(
os.path.dirname(cdci_data_analysis.__file__),
"config_dir/conf_env.yml.example"
)
with open(example_config_fn) as example_config_fn_f:
example_config = yaml.load(example_config_fn_f, Loader=yaml.SafeLoader)['dispatcher']
example_config.pop('product_gallery_options', None)
example_config.pop('matrix_options', None)

mapper = lambda x, y: ".".join(map(str, x))
example_config_keys = flatten_nested_structure(example_config, mapper)
test_config_keys = flatten_nested_structure(dispatcher_test_conf_with_vo_options, mapper)

print("\n\n\nexample_config_keys", example_config_keys)
print("\n\n\ntest_config_keys", test_config_keys)

assert set(example_config_keys) == set(test_config_keys)


def test_image(dispatcher_live_fixture):
server = dispatcher_live_fixture

Expand Down
Loading