chore: Adds non-interactive mode to superset-extensions init command (#36308)

This commit is contained in:
Michael S. Molina
2025-12-01 15:45:49 -05:00
committed by GitHub
parent b12f5f8394
commit db995ad5bf
2 changed files with 182 additions and 8 deletions

View File

@@ -404,19 +404,57 @@ def dev(ctx: click.Context) -> None:
@app.command()
def init() -> None:
id_ = click.prompt("Extension ID (unique identifier, alphanumeric only)", type=str)
@click.option(
"--id",
"id_opt",
default=None,
help="Extension ID (alphanumeric and underscores only)",
)
@click.option("--name", "name_opt", default=None, help="Extension display name")
@click.option(
"--version", "version_opt", default=None, help="Initial version (default: 0.1.0)"
)
@click.option(
"--license", "license_opt", default=None, help="License (default: Apache-2.0)"
)
@click.option(
"--frontend/--no-frontend", "frontend_opt", default=None, help="Include frontend"
)
@click.option(
"--backend/--no-backend", "backend_opt", default=None, help="Include backend"
)
def init(
id_opt: str | None,
name_opt: str | None,
version_opt: str | None,
license_opt: str | None,
frontend_opt: bool | None,
backend_opt: bool | None,
) -> None:
id_ = id_opt or click.prompt(
"Extension ID (unique identifier, alphanumeric only)", type=str
)
if not re.match(r"^[a-zA-Z0-9_]+$", id_):
click.secho(
"❌ ID must be alphanumeric (letters, digits, underscore).", fg="red"
)
sys.exit(1)
name = click.prompt("Extension name (human-readable display name)", type=str)
version = click.prompt("Initial version", default="0.1.0")
license = click.prompt("License", default="Apache-2.0")
include_frontend = click.confirm("Include frontend?", default=True)
include_backend = click.confirm("Include backend?", default=True)
name = name_opt or click.prompt(
"Extension name (human-readable display name)", type=str
)
version = version_opt or click.prompt("Initial version", default="0.1.0")
license_ = license_opt or click.prompt("License", default="Apache-2.0")
include_frontend = (
frontend_opt
if frontend_opt is not None
else click.confirm("Include frontend?", default=True)
)
include_backend = (
backend_opt
if backend_opt is not None
else click.confirm("Include backend?", default=True)
)
target_dir = Path.cwd() / id_
if target_dir.exists():
@@ -431,7 +469,7 @@ def init() -> None:
"name": name,
"include_frontend": include_frontend,
"include_backend": include_backend,
"license": license,
"license": license_,
"version": version,
}

View File

@@ -360,3 +360,139 @@ def test_full_init_workflow_integration(cli_runner, isolated_filesystem):
pyproject_content = (extension_path / "backend" / "pyproject.toml").read_text()
assert "awesome_charts" in pyproject_content
# Non-interactive mode tests
@pytest.mark.cli
def test_init_non_interactive_with_all_options(cli_runner, isolated_filesystem):
"""Test that init works in non-interactive mode with all CLI options."""
result = cli_runner.invoke(
app,
[
"init",
"--id",
"my_ext",
"--name",
"My Extension",
"--version",
"1.0.0",
"--license",
"MIT",
"--frontend",
"--backend",
],
)
assert result.exit_code == 0, f"Command failed with output: {result.output}"
assert "🎉 Extension My Extension (ID: my_ext) initialized" in result.output
extension_path = isolated_filesystem / "my_ext"
assert_directory_exists(extension_path)
assert_directory_exists(extension_path / "frontend")
assert_directory_exists(extension_path / "backend")
extension_json = load_json_file(extension_path / "extension.json")
assert extension_json["id"] == "my_ext"
assert extension_json["name"] == "My Extension"
assert extension_json["version"] == "1.0.0"
assert extension_json["license"] == "MIT"
@pytest.mark.cli
def test_init_frontend_only_with_cli_options(cli_runner, isolated_filesystem):
"""Test init with frontend only using CLI options."""
result = cli_runner.invoke(
app,
[
"init",
"--id",
"frontend_ext",
"--name",
"Frontend Extension",
"--version",
"1.0.0",
"--license",
"MIT",
"--frontend",
"--no-backend",
],
)
assert result.exit_code == 0, f"Command failed with output: {result.output}"
extension_path = isolated_filesystem / "frontend_ext"
assert_directory_exists(extension_path / "frontend")
assert not (extension_path / "backend").exists()
@pytest.mark.cli
def test_init_backend_only_with_cli_options(cli_runner, isolated_filesystem):
"""Test init with backend only using CLI options."""
result = cli_runner.invoke(
app,
[
"init",
"--id",
"backend_ext",
"--name",
"Backend Extension",
"--version",
"1.0.0",
"--license",
"MIT",
"--no-frontend",
"--backend",
],
)
assert result.exit_code == 0, f"Command failed with output: {result.output}"
extension_path = isolated_filesystem / "backend_ext"
assert not (extension_path / "frontend").exists()
assert_directory_exists(extension_path / "backend")
@pytest.mark.cli
def test_init_prompts_for_missing_options(cli_runner, isolated_filesystem):
"""Test that init prompts for options not provided via CLI and uses defaults."""
# Provide id and name via CLI, but version/license will be prompted (accept defaults)
result = cli_runner.invoke(
app,
[
"init",
"--id",
"default_ext",
"--name",
"Default Extension",
"--frontend",
"--backend",
],
input="\n\n", # Accept defaults for version and license prompts
)
assert result.exit_code == 0, f"Command failed with output: {result.output}"
extension_path = isolated_filesystem / "default_ext"
extension_json = load_json_file(extension_path / "extension.json")
assert extension_json["version"] == "0.1.0"
assert extension_json["license"] == "Apache-2.0"
@pytest.mark.cli
def test_init_non_interactive_validates_id(cli_runner, isolated_filesystem):
"""Test that non-interactive mode validates extension ID."""
result = cli_runner.invoke(
app,
[
"init",
"--id",
"invalid-id",
"--name",
"Invalid Extension",
"--frontend",
"--backend",
],
)
assert result.exit_code == 1
assert "must be alphanumeric" in result.output