--- a +++ b/qiita_db/handlers/analysis.py @@ -0,0 +1,94 @@ +# ----------------------------------------------------------------------------- +# Copyright (c) 2014--, The Qiita Development Team. +# +# Distributed under the terms of the BSD 3-clause License. +# +# The full license is in the file LICENSE, distributed with this software. +# ----------------------------------------------------------------------------- + +from tornado import gen +from tornado.web import HTTPError +from json import dumps + +import qiita_db as qdb +from .oauth2 import OauthBaseHandler, authenticate_oauth + + +def _get_analysis(a_id): + """Returns the analysis with the given `a_id` if it exists + + Parameters + ---------- + a_id : str + The analysis id + + Returns + ------- + qiita_db.analysis.Analysis + The requested analysis + + Raises + ------ + HTTPError + If the analysis does not exist, with error code 404 + If there is a problem instantiating the analysis, with error code 500 + """ + try: + a_id = int(a_id) + a = qdb.analysis.Analysis(a_id) + except qdb.exceptions.QiitaDBUnknownIDError: + raise HTTPError(404) + except Exception as e: + raise HTTPError(500, reason='Error instantiating analysis %s: %s' + % (a_id, str(e))) + return a + + +class APIAnalysisMetadataHandler(OauthBaseHandler): + @authenticate_oauth + async def get(self, analysis_id): + """Retrieves the analysis metadata + + Parameters + ---------- + analysis_id : str + The id of the analysis whose information is being retrieved + + Returns + ------- + dict + The contents of the analysis keyed by sample id + + Notes + ----- + This response needed to be broken in chunks because we were hitting + the max size of a respose: 2G; based on: https://bit.ly/3CPvyjd + """ + chunk_len = 1024 * 1024 * 1 # 1 MiB + + response = None + with qdb.sql_connection.TRN: + a = _get_analysis(analysis_id) + mf_fp = qdb.util.get_filepath_information( + a.mapping_file)['fullpath'] + if mf_fp is not None: + df = qdb.metadata_template.util.load_template_to_dataframe( + mf_fp, index='#SampleID') + response = dumps(df.to_dict(orient='index')) + + if response is not None: + crange = range(chunk_len, len(response)+chunk_len, chunk_len) + for i, (win) in enumerate(crange): + # sending the chunk and flushing + chunk = response[i*chunk_len:win] + self.write(chunk) + await self.flush() + + # cleaning chuck and pause the coroutine so other handlers + # can run, note that this is required/important based on the + # original implementation in https://bit.ly/3CPvyjd + del chunk + await gen.sleep(0.000000001) # 1 nanosecond + + else: + self.write(None)