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 42 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
42 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
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 ..flask_app.sentry import sentry
Fixed Show fixed Hide fixed
from ..app_logging import app_logging

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 parsing ADQL query: {qe}')
output_obj = dict(

Check warning on line 25 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-L25

Added lines #L23 - L25 were not covered by tests
mysql_query=None,
psql_query=None
)
return output_obj

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

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L29

Added line #L29 was not covered by tests


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

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

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L33

Added line #L33 was not covered by tests

# TODO use a specific dedicated table and schema to refer to the product_gallery DB ?
# tables = parsed_query_obj.get('tables', [])
# if len(tables) == 1 and tables[0] == 'product_gallery':
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 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#L38-L44

Added lines #L38 - L44 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 50 in cdci_data_analysis/analysis/ivoa_helper.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L50

Added line #L50 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 = []

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

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L60

Added line #L60 was not covered by tests

try:
with connect(

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

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/analysis/ivoa_helper.py#L62-L63

Added lines #L62 - L63 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 {'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#L69-L81

Added lines #L69 - L81 were not covered by tests

except (Exception, DatabaseError) as e:
sentry.capture_message(f"Error when querying to the Postgresql server: {str(e)}")
logger.error(f"Error when querying to the Postgresql server: {str(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 @@ -122,4 +122,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 @@ -266,6 +266,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 @@ -344,6 +352,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 @@ -395,6 +411,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
37 changes: 36 additions & 1 deletion 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 @@ -430,6 +430,41 @@
"Our team is notified and is working on it.")


@app.route('/run_adql_query')
def run_adql_query():
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 437 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-L437

Added lines #L435 - L437 were not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L439

Added line #L439 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 443 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L441-L443

Added lines #L441 - L443 were not covered by tests

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

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

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L445

Added line #L445 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 450 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L449-L450

Added lines #L449 - L450 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 457 in cdci_data_analysis/flask_app/app.py

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L452-L457

Added lines #L452 - L457 were not covered by tests

result_query = ivoa_helper.run_ivoa_query(adql_query,

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

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L459

Added line #L459 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)

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

View check run for this annotation

Codecov / codecov/patch

cdci_data_analysis/flask_app/app.py#L466

Added line #L466 was not covered by tests


@app.route('/run_analysis', methods=['POST', 'GET'])
def run_analysis():
Expand Down
27 changes: 27 additions & 0 deletions cdci_data_analysis/pytest_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,27 @@
yield fn


@pytest.fixture
def dispatcher_test_conf_with_vo_options_fn(dispatcher_test_conf_fn):
fn = "test-dispatcher-conf-with-vo-options.yaml"

with open(fn, "w") as f:
with open(dispatcher_test_conf_fn) as f_default:
f.write(f_default.read())

f.write('\n vo_options:'
'\n vo_mysql_pg_host: "localhost"'
'\n vo_mysql_pg_user: "user"'
'\n vo_mysql_pg_password: "password"'
'\n vo_mysql_pg_db: "database"'
'\n vo_psql_pg_host: "localhost"'
'\n vo_psql_pg_user: "user"'
'\n vo_psql_pg_password: "password"'
'\n vo_psql_pg_db: "database"')

yield fn


@pytest.fixture
def dispatcher_test_conf_with_matrix_options_fn(dispatcher_test_conf_fn):
fn = "test-dispatcher-conf-with-matrix-options.yaml"
Expand Down Expand Up @@ -708,10 +729,16 @@
yield yaml.load(open(dispatcher_test_conf_with_gallery_fn), Loader=yaml.SafeLoader)['dispatcher']


@pytest.fixture
def dispatcher_test_conf_with_vo_options(dispatcher_test_conf_with_vo_options_fn):
yield yaml.load(open(dispatcher_test_conf_with_vo_options_fn), Loader=yaml.SafeLoader)['dispatcher']

Check warning

Code scanning / CodeQL

File is not always closed Warning

File is opened but is not closed.


@pytest.fixture
def dispatcher_test_conf_with_matrix_options(dispatcher_test_conf_with_matrix_options_fn):
yield yaml.load(open(dispatcher_test_conf_with_matrix_options_fn), Loader=yaml.SafeLoader)['dispatcher']


@pytest.fixture
def dispatcher_test_conf_with_gallery_no_resolver(dispatcher_test_conf_with_gallery_no_resolver_fn):
yield yaml.load(open(dispatcher_test_conf_with_gallery_no_resolver_fn), Loader=yaml.SafeLoader)['dispatcher']
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
2 changes: 2 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@
gunicorn_dispatcher_long_living_fixture_with_matrix_options,
dispatcher_test_conf,
dispatcher_test_conf_with_gallery,
dispatcher_test_conf_with_vo_options,
dispatcher_test_conf_with_gallery_no_resolver,
dispatcher_test_conf_empty_sentry_fn,
dispatcher_test_conf_with_gallery_fn,
dispatcher_test_conf_with_vo_options_fn,
dispatcher_test_conf_with_gallery_no_resolver_fn,
dispatcher_live_fixture_with_external_products_url,
dispatcher_live_fixture_with_default_route_products_url,
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 @@ -2407,6 +2407,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 @@ -2428,6 +2429,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 @@ -2449,6 +2451,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 @@ -2460,6 +2463,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