diff --git a/docs/source/cli.rst b/docs/source/cli.rst index d2f1920e..40956fa9 100644 --- a/docs/source/cli.rst +++ b/docs/source/cli.rst @@ -21,8 +21,8 @@ Usage Extended Description ^^^^^^^^^^^^^^^^^^^^ -The :samp:`openalchemy build` command builds a reusable Python package based off of -the specification file. +The :samp:`openalchemy build` command builds a reusable Python package based off +of the specification file. For instance, running the following command:: @@ -54,3 +54,29 @@ Options +-----------------+--------------+-------------------------------------------+ | --format, -f | sdist, wheel | limit the format to either sdist or wheel | +-----------------+--------------+-------------------------------------------+ + +openalchemy generate +--------------------- + +Description +^^^^^^^^^^^ + +Generate the models described in the OpenAPI specification file. + +Usage +^^^^^ + +.. program:: openalchemy + +.. option:: openalchemy generate SPECFILE OUTPUT_FILE + + +Extended Description +^^^^^^^^^^^^^^^^^^^^ + +The :samp:`openalchemy generate` command generates the models without having to +start an application. + +Example:: + + openalchemy generate openapi.yml models.py diff --git a/open_alchemy/cli.py b/open_alchemy/cli.py index a1d01b35..47681d5b 100644 --- a/open_alchemy/cli.py +++ b/open_alchemy/cli.py @@ -7,6 +7,8 @@ from open_alchemy import build_json from open_alchemy import build_yaml from open_alchemy import exceptions +from open_alchemy import init_json +from open_alchemy import init_yaml # Configure the logger. logging.basicConfig(format="%(message)s", level=logging.INFO) @@ -75,6 +77,18 @@ def build_application_parser() -> argparse.Namespace: ) build_parser.set_defaults(func=build) + # Define the parser for the "generate" subcommand. + generate_parser = subparsers.add_parser( + "generate", + description="Generate the SQLAlchemy models.", + help="regenerate models", + ) + generate_parser.add_argument( + "specfile", type=str, help="specify the specification file" + ) + generate_parser.add_argument("output", type=str, help="specify the output file") + generate_parser.set_defaults(func=generate) + # Return the parsed arguments for a particular command. return parser.parse_args() @@ -103,3 +117,22 @@ def build(args: argparse.Namespace) -> None: # Build the package. builder = builders.get(specfile.suffix.lower()) builder(args.specfile, args.name, args.output, fmt.get(args.format)) # type: ignore + + +def generate(args): + """ + Define the generate subcommand. + + Args: + args: CLI arguments from the parser. + """ + # Check the specfile. + specfile = pathlib.Path(args.specfile) + validate_specfile(specfile) + + # Select the generator method. + generators = dict(zip(VALID_EXTENSIONS, [init_json, init_yaml, init_yaml])) + + # Regenerate the models. + generator = generators.get(specfile.suffix.lower()) + generator(args.specfile, models_filename=args.output) diff --git a/tests/test_cli.py b/tests/test_cli.py index 32648e48..6fd93ad9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,5 +1,6 @@ """Tests for the CLI.""" +import argparse import os import pathlib import sys @@ -29,7 +30,6 @@ def test_validate_specfile_valid(tmp_path, specfile): """ spec = tmp_path / specfile spec.write_text("") - cli.validate_specfile(spec) @@ -64,9 +64,14 @@ def test_validate_specfile_does_not_exist(tmp_path): "command, expected_arguments", [ pytest.param( - ["openalchemy", "build", "my_specfile", "my_package", "my_output_dir"], - ["specfile='my_specfile'", "name='my_package'", "output='my_output_dir'"], - id="cli help command", + ["openalchemy", "build", "specfile.yaml", "my_package", "my_output_dir"], + ["specfile='specfile.yaml'", "name='my_package'", "output='my_output_dir'"], + id="cli build command", + ), + pytest.param( + ["openalchemy", "generate", "specfile.yaml", "models.py"], + ["specfile='specfile.yaml'", "output='models.py'"], + id="cli generate command", ), ], ) @@ -104,6 +109,24 @@ def test_build(mocker): m_build_json.assert_called_once() +@pytest.mark.cli +def test_generate(tmp_path): + """ + GIVEN arguments from the parser + WHEN they are passed to the generate() function + THEN the function generate models + """ + model_file = tmp_path / "models.py" + args = argparse.Namespace( + specfile=f"{pathlib.Path.cwd() / 'examples' / 'simple' / 'example-spec.yml'}", + output=str(model_file), + ) + + cli.generate(args) + + assert "Autogenerated SQLAlchemy models" in model_file.read_text() + + @pytest.mark.parametrize( "command, expected_file", [