diff --git a/pyproject.toml b/pyproject.toml index 56845ec..5a0ace6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,3 +30,6 @@ classifiers = [ [project.urls] "Homepage" = "https://github.com/MrCurtis/fk-graph" + +[project.scripts] +fk-graph = "fk_graph.cli:main" diff --git a/src/fk_graph/cli.py b/src/fk_graph/cli.py new file mode 100644 index 0000000..f86c750 --- /dev/null +++ b/src/fk_graph/cli.py @@ -0,0 +1,37 @@ +from argparse import ArgumentParser + +from sqlalchemy import create_engine + +from fk_graph import setup_data, get_graph +from fk_graph.plotly_functions import run_app + +def main(): + args = _parse_args() + if args.demo: + engine = create_engine("sqlite+pysqlite:///:memory:") + setup_data(engine) + elif args.connection_string: + engine = create_engine(args.connection_string) + graph = get_graph(engine, args.table, args.primary_key) + run_app(graph) + +def _parse_args(): + parser = ArgumentParser( + prog="fk-graph", + description="Visualise the graphs hidden within relational databases.", + + ) + parser.add_argument("--demo", action="store_true", help="Run with the built-in demo database.") + parser.add_argument("--connection-string") + parser.add_argument("--table", required=True) + parser.add_argument("--primary-key", required=True) + args = parser.parse_args() + if ( + (not args.demo and args.connection_string is None) + or + (args.demo and args.connection_string is not None) + ): + parser.error( + "Exactly one of --demo and --connection-string should be used." + ) + return args diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 0000000..910f978 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,66 @@ +from shlex import split as shlex_split +from subprocess import Popen, run +from time import sleep +from unittest import TestCase + +from requests import get +import requests +from requests.adapters import HTTPAdapter +from requests.packages.urllib3.util.retry import Retry + +import logging + +logging.basicConfig(level=logging.DEBUG) + +s = requests.Session() +retries = Retry(total=5, backoff_factor=1, status_forcelist=[502, 503, 504]) +s.mount('http://', HTTPAdapter(max_retries=retries)) +s.mount('https://', HTTPAdapter(max_retries=retries)) + +class TestCLI(TestCase): + def test_simple_case(self): + command = "fk-graph --demo --table=table_a --primary-key=1" + with Popen(shlex_split(command)) as process: + response = s.get("http://localhost:8050") + process.terminate() + # Because of the javascripty-nature of the app, we can + # only really inspect the status code. + self.assertEqual(response.status_code, 200) + + def test_errors_if_both_demo_and_connection_string_included(self): + command = ( + "fk-graph" + " --demo" + " --connection-string=\"sqlite+pysqlite:///:memory:\"" + " --table=table_a" + " --primary-key=1" + ) + completed_process = run( + shlex_split(command), + capture_output=True, + text=True, + timeout=5 + ) + self.assertEqual(completed_process.returncode, 2) + self.assertIn( + "Exactly one of --demo and --connection-string should be used.", + completed_process.stderr + ) + + def test_errors_if_neither_demo_or_connection_string_included(self): + command = ( + "fk-graph" + " --table=table_a" + " --primary-key=1" + ) + completed_process = run( + shlex_split(command), + capture_output=True, + text=True, + timeout=5 + ) + self.assertEqual(completed_process.returncode, 2) + self.assertIn( + "Exactly one of --demo and --connection-string should be used.", + completed_process.stderr + )