diff --git a/exportsrv/app.py b/exportsrv/app.py index 55e33df..3eafe99 100644 --- a/exportsrv/app.py +++ b/exportsrv/app.py @@ -5,7 +5,17 @@ from adsmutils import ADSFlask -from exportsrv.views import bp +from exportsrv.views import bp, endpoint_registry + +def attach_routes_to_registry(app): + for rule in app.url_map.iter_rules(): + if rule.endpoint == 'static': + continue + view_func = app.view_functions[rule.endpoint] + for name, entry in endpoint_registry.items(): + if view_func in entry["handlers"]: + if rule.rule not in entry["routes"]: + entry["routes"].append(rule.rule) def create_app(**config): """ @@ -23,6 +33,8 @@ def create_app(**config): Discoverer(app) app.register_blueprint(bp) + + attach_routes_to_registry(app) return app if __name__ == '__main__': diff --git a/exportsrv/tests/unittests/test_export_service.py b/exportsrv/tests/unittests/test_export_service.py index 3ec23c4..a7aa6c2 100755 --- a/exportsrv/tests/unittests/test_export_service.py +++ b/exportsrv/tests/unittests/test_export_service.py @@ -1229,6 +1229,69 @@ def test_output_format_individual(self): exported = XMLFormat(solrdata.data).get_dublincore_xml(output_format=adsOutputFormat.individual) assert (exported == xmlTest.data_dublin_core_individual) + def test_manifest(self): + response = self.client.get('/manifest') + assert (response.json[0] == {'name': 'BibTeX', 'type': 'tagged', 'route': '/bibtex'}) + +class TestRegistryCoverage(unittest.TestCase): + def setUp(self): + self.current_app = app.create_app() + app.attach_routes_to_registry(self.current_app) + self.endpoint_registry = views.endpoint_registry + + # Collect view functions from Flask that are relevant (excluding static, /manifest) + self.flask_view_funcs = { + rule.endpoint: self.current_app.view_functions[rule.endpoint] + for rule in self.current_app.url_map.iter_rules() + if rule.endpoint != 'static' and not rule.rule.startswith("/manifest") + } + + # Set of handlers from Flask + self.flask_view_set = set(self.flask_view_funcs.values()) + + # Set of handlers from the registry + self.registry_handler_set = set( + handler + for entry in self.endpoint_registry.values() + for handler in entry.get("handlers", []) + ) + + def test_all_registered_handlers_are_in_flask(self): + """All handlers in the registry must be real Flask view functions.""" + missing = [] + for name, entry in self.endpoint_registry.items(): + for handler in entry.get("handlers", []): + if handler not in self.flask_view_set: + missing.append((name, handler.__name__)) + if missing: + self.fail(f"The following registry entries are not registered Flask views: {missing}") + + def test_all_flask_views_are_in_registry(self): + """All relevant Flask view functions must appear in the endpoint registry.""" + # Acceptable views that are intentionally not decorated/registered + ignored_names = {"ready", "alive", ""} + + # we have one registry entry for every pair of GET/POST endpoints + ignored_suffixes = ("_get", "_post") + missing_handlers = [ + func for func in self.flask_view_set + if func not in self.registry_handler_set + and not func.__name__.endswith(ignored_suffixes) + and func.__name__ not in ignored_names + ] + if missing_handlers: + missing_names = [func.__name__ for func in missing_handlers] + self.fail(f"These Flask view functions are not in the endpoint registry: {missing_names}") + + def test_all_registry_entries_have_routes(self): + """Ensure that each endpoint in the registry has at least one route recorded.""" + missing = [] + for name, entry in self.endpoint_registry.items(): + if not entry.get("routes"): + missing.append(name) + if missing: + self.fail(f"The following registry entries are missing route information: {missing}") + if __name__ == '__main__': unittest.main() diff --git a/exportsrv/views.py b/exportsrv/views.py index 84fb2a0..dd27a46 100755 --- a/exportsrv/views.py +++ b/exportsrv/views.py @@ -19,7 +19,17 @@ bp = Blueprint('export_service', __name__) - +endpoint_registry = {} # name -> { type, handlers, routes } +def register_endpoint(name: str, type_: str): + types = ['tagged', 'LaTeX', 'XML', 'text', 'custom'] + def decorator(func): + if type_ not in types: + raise ValueError(f"Type {type_} for format {name} is not an allowed value.") + entry = endpoint_registry.setdefault(name, {"type": type_, "handlers": [], "routes": []}) + if func not in entry["handlers"]: + entry["handlers"].append(func) + return func + return decorator def default_solr_fields(author_limit=0): """ @@ -359,6 +369,7 @@ def export_get(bibcode, style, format=-1): return get_solr_data(bibcodes=[bibcode], fields=default_solr_fields(), sort=sort, encode_style=adsFormatter().native_encoding(format)) +@register_endpoint('BibTeX', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/bibtex', methods=['POST']) def bibTex_format_export_post(): @@ -378,7 +389,7 @@ def bibTex_format_export_post(): journal_format=journal_format, export_output_format=export_output_format) return return_response(results, status) - +@register_endpoint('BibTeX', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/bibtex/', methods=['GET']) def bibTex_format_export_get(bibcode): @@ -391,7 +402,7 @@ def bibTex_format_export_get(bibcode): keyformat='%R', max_author=10, author_cutoff=200, journal_format=1, export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('BibTeX ABS', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/bibtexabs', methods=['POST']) def bibTex_abs_format_export_post(): @@ -411,7 +422,7 @@ def bibTex_abs_format_export_post(): journal_format=journal_format, export_output_format=export_output_format) return return_response(results, status) - +@register_endpoint('BibTeX ABS', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/bibtexabs/', methods=['GET']) def bibTex_abs_format_export_get(bibcode): @@ -424,7 +435,7 @@ def bibTex_abs_format_export_get(bibcode): keyformat='%R', max_author=0, author_cutoff=200, journal_format=1, export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('ADS', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/ads', methods=['POST']) def fielded_ads_format_export_post(): @@ -441,7 +452,7 @@ def fielded_ads_format_export_post(): return return_fielded_format_export(solr_data=results, fielded_style='ADS', export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('ADS', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/ads/', methods=['GET']) def fielded_ads_format_export_get(bibcode): @@ -452,7 +463,7 @@ def fielded_ads_format_export_get(bibcode): """ return return_fielded_format_export(solr_data=export_get(bibcode, 'ADS'), fielded_style='ADS', export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('EndNote', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/endnote', methods=['POST']) def fielded_endnote_format_export_post(): @@ -469,7 +480,7 @@ def fielded_endnote_format_export_post(): return return_fielded_format_export(solr_data=results, fielded_style='EndNote', export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('EndNote', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/endnote/', methods=['GET']) def fielded_endnote_format_export_get(bibcode): @@ -480,7 +491,7 @@ def fielded_endnote_format_export_get(bibcode): """ return return_fielded_format_export(solr_data=export_get(bibcode, 'EndNote'), fielded_style='EndNote', export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('ProCite', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/procite', methods=['POST']) def fielded_procite_format_export_post(): @@ -497,7 +508,7 @@ def fielded_procite_format_export_post(): return return_fielded_format_export(solr_data=results, fielded_style='ProCite', export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('ProCite', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/procite/', methods=['GET']) def fielded_procite_format_export_get(bibcode): @@ -508,7 +519,7 @@ def fielded_procite_format_export_get(bibcode): """ return return_fielded_format_export(solr_data=export_get(bibcode, 'ProCite'), fielded_style='ProCite', export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('RIS', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/ris', methods=['POST']) def fielded_refman_format_export_post(): @@ -525,7 +536,7 @@ def fielded_refman_format_export_post(): return return_fielded_format_export(solr_data=results, fielded_style='Refman', export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('RIS', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/ris/', methods=['GET']) def fielded_refman_format_export_get(bibcode): @@ -536,7 +547,7 @@ def fielded_refman_format_export_get(bibcode): """ return return_fielded_format_export(solr_data=export_get(bibcode, 'Refman'), fielded_style='Refman', export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('RefWorks', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/refworks', methods=['POST']) def fielded_refworks_format_export_post(): @@ -553,7 +564,7 @@ def fielded_refworks_format_export_post(): return return_fielded_format_export(solr_data=results, fielded_style='RefWorks', export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('RefWorks', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/refworks/', methods=['GET']) def fielded_refworks_format_export_get(bibcode): @@ -564,7 +575,7 @@ def fielded_refworks_format_export_get(bibcode): """ return return_fielded_format_export(solr_data=export_get(bibcode, 'RefWorks'), fielded_style='RefWorks', export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('MEDLARS', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/medlars', methods=['POST']) def fielded_medlars_format_export_post(): @@ -581,7 +592,7 @@ def fielded_medlars_format_export_post(): return return_fielded_format_export(solr_data=results, fielded_style='MEDLARS', export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('MEDLARS', 'tagged') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/medlars/', methods=['GET']) def fielded_medlars_format_export_get(bibcode): @@ -592,7 +603,7 @@ def fielded_medlars_format_export_get(bibcode): """ return return_fielded_format_export(solr_data=export_get(bibcode, 'MEDLARS'), fielded_style='MEDLARS', export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('DC-XML', 'XML') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/dcxml', methods=['POST']) def xml_dublincore_format_export_post(): @@ -609,7 +620,7 @@ def xml_dublincore_format_export_post(): return return_xml_format_export(solr_data=results, xml_style='DublinCore', export_output_format=export_output_format) return return_response(results, status) - +@register_endpoint('DC-XML', 'XML') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/dcxml/', methods=['GET']) def xml_dublincore_format_export_get(bibcode): @@ -620,7 +631,7 @@ def xml_dublincore_format_export_get(bibcode): """ return return_xml_format_export(solr_data=export_get(bibcode, 'DublinCore'), xml_style='DublinCore', export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('REF-XML', 'XML') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/refxml', methods=['POST']) def xml_ref_format_export_post(): @@ -637,7 +648,7 @@ def xml_ref_format_export_post(): return return_xml_format_export(solr_data=results, xml_style='Reference', export_output_format=export_output_format) return return_response(results, status) - +@register_endpoint('REF-XML', 'XML') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/refxml/', methods=['GET']) def xml_ref_format_export_get(bibcode): @@ -648,7 +659,7 @@ def xml_ref_format_export_get(bibcode): """ return return_xml_format_export(solr_data=export_get(bibcode, 'Reference'), xml_style='Reference', export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('REFABS-XML', 'XML') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/refabsxml', methods=['POST']) def xml_refabs_format_export_post(): @@ -665,7 +676,7 @@ def xml_refabs_format_export_post(): return return_xml_format_export(solr_data=results, xml_style='ReferenceAbs', export_output_format=export_output_format) return return_response(results, status) - +@register_endpoint('REFABS-XML', 'XML') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/refabsxml/', methods=['GET']) def xml_refabs_format_export_get(bibcode): @@ -676,7 +687,7 @@ def xml_refabs_format_export_get(bibcode): """ return return_xml_format_export(solr_data=export_get(bibcode, 'ReferenceAbs'), xml_style='ReferenceAbs', export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('JATS-XML', 'XML') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/jatsxml', methods=['POST']) def xml_jats_format_export_post(): @@ -693,7 +704,7 @@ def xml_jats_format_export_post(): return return_xml_format_export(solr_data=results, xml_style='JATS', export_output_format=export_output_format) return return_response(results, status) - +@register_endpoint('JATS-XML', 'XML') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/jatsxml/', methods=['GET']) def xml_jats_format_export_get(bibcode): @@ -704,7 +715,7 @@ def xml_jats_format_export_get(bibcode): """ return return_xml_format_export(solr_data=export_get(bibcode, 'JATS'), xml_style='JATS', export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('AASTeX', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/aastex', methods=['POST']) def csl_aastex_format_export_post(): @@ -724,7 +735,7 @@ def csl_aastex_format_export_post(): export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('AASTeX', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/aastex/', methods=['GET']) def csl_aastex_format_export_get(bibcode): @@ -737,6 +748,7 @@ def csl_aastex_format_export_get(bibcode): csl_style='aastex', export_format=adsFormatter.latex, journal_format=adsJournalFormat.macro, export_output_format=adsOutputFormat.default, request_type='GET') +@register_endpoint('AASTeX (PSJ)', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/aastex-psj', methods=['POST']) def csl_aastex_psj_format_export_post(): @@ -756,7 +768,7 @@ def csl_aastex_psj_format_export_post(): export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('AASTeX (PSJ)', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/aastex-psj/', methods=['GET']) def csl_aastex_psj_format_export_get(bibcode): @@ -769,6 +781,7 @@ def csl_aastex_psj_format_export_get(bibcode): csl_style='aastex-psj', export_format=adsFormatter.latex, journal_format=adsJournalFormat.macro, export_output_format=adsOutputFormat.default, request_type='GET') +@register_endpoint('Icarus', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/icarus', methods=['POST']) def csl_icarus_format_export_post(): @@ -787,7 +800,7 @@ def csl_icarus_format_export_post(): export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('Icarus', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/icarus/', methods=['GET']) def csl_icarus_format_export_get(bibcode): @@ -800,7 +813,7 @@ def csl_icarus_format_export_get(bibcode): csl_style='icarus', export_format=adsFormatter.latex, journal_format=adsJournalFormat.full, export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('MNRAS', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/mnras', methods=['POST']) def csl_mnras_format_export_post(): @@ -819,7 +832,7 @@ def csl_mnras_format_export_post(): export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('MNRAS', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/mnras/', methods=['GET']) def csl_mnras_format_export_get(bibcode): @@ -832,7 +845,7 @@ def csl_mnras_format_export_get(bibcode): csl_style='mnras', export_format=adsFormatter.latex, journal_format=adsJournalFormat.full, export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('Solar Physics', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/soph', methods=['POST']) def csl_soph_format_export_post(): @@ -851,7 +864,7 @@ def csl_soph_format_export_post(): export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('Solar Physics', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/soph/', methods=['GET']) def csl_soph_format_export_get(bibcode): @@ -864,7 +877,7 @@ def csl_soph_format_export_get(bibcode): csl_style='soph', export_format=adsFormatter.latex, journal_format=adsJournalFormat.full, export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('ASPC', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/aspc', methods=['POST']) def csl_aspc_format_export_post(): @@ -883,7 +896,7 @@ def csl_aspc_format_export_post(): export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('ASP Conference', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/aspc/', methods=['GET']) def csl_aspc_format_export_get(bibcode): @@ -896,7 +909,7 @@ def csl_aspc_format_export_get(bibcode): csl_style='aspc', export_format=adsFormatter.latex, journal_format=adsJournalFormat.full, export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('AAS Journals', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/aasj', methods=['POST']) def csl_aasj_format_export_post(): @@ -915,7 +928,7 @@ def csl_aasj_format_export_post(): export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('AAS Journals', 'LaTeX') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/aasj/', methods=['GET']) def csl_aasj_format_export_get(bibcode): @@ -928,7 +941,7 @@ def csl_aasj_format_export_get(bibcode): csl_style='aasj', export_format=adsFormatter.latex, journal_format=adsJournalFormat.full, export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('APS Journals', 'text') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/apsj', methods=['POST']) def csl_apsj_format_export_post(): @@ -947,7 +960,7 @@ def csl_apsj_format_export_post(): export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('APS Journals', 'text') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/apsj/', methods=['GET']) def csl_apsj_format_export_get(bibcode): @@ -960,7 +973,7 @@ def csl_apsj_format_export_get(bibcode): csl_style='apsj', export_format=adsFormatter.unicode, journal_format=adsJournalFormat.full, export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('IEEE', 'text') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/ieee', methods=['POST']) def csl_ieee_format_export_post(): @@ -979,7 +992,7 @@ def csl_ieee_format_export_post(): export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('IEEE', 'text') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/ieee/', methods=['GET']) def csl_ieee_format_export_get(bibcode): @@ -992,7 +1005,7 @@ def csl_ieee_format_export_get(bibcode): csl_style='ieee', export_format=adsFormatter.unicode, journal_format=adsJournalFormat.full, export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('AGU', 'text') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/agu', methods=['POST']) def csl_agu_format_export_post(): @@ -1011,7 +1024,7 @@ def csl_agu_format_export_post(): export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('AGU', 'text') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/agu/', methods=['GET']) def csl_agu_format_export_get(bibcode): @@ -1024,7 +1037,7 @@ def csl_agu_format_export_get(bibcode): csl_style='agu', export_format=adsFormatter.unicode, journal_format=adsJournalFormat.full, export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('GSA', 'text') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/gsa', methods=['POST']) def csl_gsa_format_export_post(): @@ -1043,7 +1056,7 @@ def csl_gsa_format_export_post(): export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('GSA', 'text') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/gsa/', methods=['GET']) def csl_gsa_format_export_get(bibcode): @@ -1056,7 +1069,7 @@ def csl_gsa_format_export_get(bibcode): csl_style='gsa', export_format=adsFormatter.unicode, journal_format=adsJournalFormat.full, export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('AMS (Meteorological)', 'text') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/ams', methods=['POST']) def csl_ams_format_export_post(): @@ -1075,7 +1088,7 @@ def csl_ams_format_export_post(): export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('AMS (Meteorological)', 'text') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/ams/', methods=['GET']) def csl_ams_format_export_get(bibcode): @@ -1088,7 +1101,7 @@ def csl_ams_format_export_get(bibcode): csl_style='ams', export_format=adsFormatter.unicode, journal_format=adsJournalFormat.full, export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('CSL', 'custom') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/csl', methods=['POST']) def csl_format_export(): @@ -1121,7 +1134,7 @@ def csl_format_export(): return return_response(results, status) - +@register_endpoint('custom', 'custom') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/custom', methods=['POST']) def custom_format_export(): @@ -1153,7 +1166,7 @@ def custom_format_export(): return return_response(results, status) - +@register_endpoint('VOTable', 'XML') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/votable', methods=['POST']) def votable_format_export_post(): @@ -1170,7 +1183,7 @@ def votable_format_export_post(): return return_votable_format_export(solr_data=results, export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('VOTable', 'XML') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/votable/', methods=['GET']) def votable_format_export_get(bibcode): @@ -1181,7 +1194,7 @@ def votable_format_export_get(bibcode): """ return return_votable_format_export(solr_data=export_get(bibcode, 'VOTable'), export_output_format=adsOutputFormat.default, request_type='GET') - +@register_endpoint('RSS', 'XML') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/rss', methods=['POST']) def rss_format_export_post(): @@ -1202,7 +1215,7 @@ def rss_format_export_post(): return return_rss_format_export(solr_data=results, link=link, export_output_format=export_output_format, request_type='POST') return return_response(results, status) - +@register_endpoint('RSS', 'XML') @advertise(scopes=[], rate_limit=[1000, 3600 * 24]) @bp.route('/rss//', defaults={'link': ''}, methods=['GET']) @bp.route('/rss//', methods=['GET']) @@ -1214,3 +1227,23 @@ def rss_format_export_get(bibcode, link): :return: """ return return_rss_format_export(solr_data=export_get(bibcode, 'RSS'), link=link, export_output_format=adsOutputFormat.default, request_type='GET') + +@advertise(scopes=[], rate_limit=[1000, 3600 * 24]) +@bp.route('/manifest', methods=['GET']) +def export_manifest_get(): + """ + Returns dict of available export formats with their format type + """ + results = [] + for name, info in endpoint_registry.items(): + routes = info.get("routes", []) + route = routes[0] if routes else None + results.append({ + "name": name, + "type": info.get("type"), + "route": route + }) + + # using POST here returns JSON + return return_response(results, 200, request_type='POST') +