Skip to content

Commit

Permalink
Add converter CLI (#30)
Browse files Browse the repository at this point in the history
  • Loading branch information
cthoyt authored Jan 14, 2023
1 parent 867702c commit 95ef0c5
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 3 deletions.
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ recursive-include docs/source *.png
global-exclude *.py[cod] __pycache__ *.so *.dylib .DS_Store *.gpickle

include README.md LICENSE
exclude tox.ini .flake8 .bumpversion.cfg .readthedocs.yml
exclude tox.ini .flake8 .bumpversion.cfg .readthedocs.yml codecov.yml
3 changes: 3 additions & 0 deletions codecov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ignore:
- "src/curies/__main__.py"
- "src/curies/cli.py"
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ exclude =
.cache,
.eggs,
data
per-file-ignores =
src/curies/cli.py:DAR101,DAR201
max-line-length = 120
max-complexity = 20
import-order-style = pycharm
Expand Down
9 changes: 9 additions & 0 deletions src/curies/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# -*- coding: utf-8 -*-
# type:ignore

"""Command line interface for ``curies``."""

from .cli import main

if __name__ == "__main__":
main()
108 changes: 108 additions & 0 deletions src/curies/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
# type:ignore

"""Command line interface for ``curies``."""

import sys
from typing import Callable, Mapping

import click

from curies import Converter, sources

__all__ = [
"main",
]

LOADERS = {
"jsonld": Converter.from_jsonld,
"prefix_map": Converter.from_prefix_map,
"extended_prefix_map": Converter.from_extended_prefix_map,
"reverse_prefix_map": Converter.from_reverse_prefix_map,
"priority_prefix_map": Converter.from_priority_prefix_map,
}

CONVERTERS: Mapping[str, Callable[[], Converter]] = {
"bioregistry": sources.get_bioregistry_converter,
"go": sources.get_go_converter,
"monarch": sources.get_monarch_converter,
"obo": sources.get_obo_converter,
"prefixcommons": sources.get_prefixcommons_converter,
}


def _get_app(converter: Converter, framework: str):
if framework == "flask":
from curies import get_flask_app

return get_flask_app(converter)
elif framework == "fastapi":
from curies import get_fastapi_app

return get_fastapi_app(converter)
else:
raise ValueError(f"Unhandled framework: {framework}")


def _run_app(app, server, host, port):
if server == "uvicorn":
import uvicorn

uvicorn.run(app, host=host, port=port)
elif server == "werkzeug":
app.run(host=host, port=port)
elif server == "gunicorn":
raise NotImplementedError
else:
raise ValueError(f"Unhandled server: {server}")


@click.command()
@click.argument("location")
@click.option(
"--framework",
default="flask",
type=click.Choice(["flask", "fastapi"]),
show_default=True,
help="The framework used to implement the app. Note, each requires different packages to be installed.",
)
@click.option(
"--server",
default="werkzeug",
type=click.Choice(["werkzeug", "uvicorn", "gunicorn"]),
show_default=True,
help="The web server used to run the app. Note, each requires different packages to be installed.",
)
@click.option(
"--format",
type=click.Choice(list(LOADERS)),
help="The data structure of the resolver data. Required if not giving a pre-defined converter name.",
)
@click.option(
"--host",
default="0.0.0.0", # noqa:S104
show_default=True,
help="The host where the resolver runs",
)
@click.option(
"--port", type=int, default=8000, show_default=True, help="The port where the resolver runs"
)
def main(location, host: str, port: int, framework: str, format: str, server: str):
"""Serve a resolver app.
Location can either be the name of a built-in converter, a file path, or a URL.
"""
if location in CONVERTERS:
converter = CONVERTERS[location]()
elif format is None:
click.secho("--format is required with remote data", fg="red")
return sys.exit(1)
else:
converter = LOADERS[format](location)

app = _get_app(converter, framework=framework)
_run_app(app, server=server, host=host, port=port)


if __name__ == "__main__":
main()
13 changes: 11 additions & 2 deletions src/curies/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@
FAILURE_CODE = 422


def _prefix_list(converter: Converter) -> str:
return "".join(f"\n- {p}" for p in sorted(converter.get_prefixes()))


def get_flask_blueprint(converter: Converter, **kwargs: Any) -> "flask.Blueprint":
"""Get a blueprint for :class:`flask.Flask`.
Expand Down Expand Up @@ -76,7 +80,9 @@ def resolve(prefix: str, identifier: str) -> "Response":
"""Resolve a CURIE."""
location = converter.expand_pair(prefix, identifier)
if location is None:
return abort(FAILURE_CODE, f"Invalid prefix: {prefix}")
return abort(
FAILURE_CODE, f"Invalid prefix: {prefix}. Use one of:{_prefix_list(converter)}"
)
return redirect(location)

return blueprint
Expand Down Expand Up @@ -204,7 +210,10 @@ def resolve(
"""Resolve a CURIE."""
location = converter.expand_pair(prefix, identifier)
if location is None:
raise HTTPException(status_code=FAILURE_CODE, detail=f"Invalid prefix: {prefix}")
raise HTTPException(
status_code=FAILURE_CODE,
detail=f"Invalid prefix: {prefix}. Use one of:{_prefix_list(converter)}",
)
return RedirectResponse(location, status_code=302)

return api_router
Expand Down

0 comments on commit 95ef0c5

Please sign in to comment.