Initial upload: mockapi - OpenAPI Mock Server Generator
This commit is contained in:
265
src/mockapi/cli/main.py
Normal file
265
src/mockapi/cli/main.py
Normal file
@@ -0,0 +1,265 @@
|
||||
"""MockAPI CLI - Main entry point."""
|
||||
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import click
|
||||
|
||||
from mockapi import __version__
|
||||
from mockapi.core.spec_loader import SpecLoader
|
||||
from mockapi.core.validator import OpenAPIValidator
|
||||
from mockapi.core.server_generator import MockServerGenerator
|
||||
from mockapi.core.config import Config
|
||||
from mockapi.core.hot_reload import HotReloader
|
||||
|
||||
|
||||
@click.group()
|
||||
@click.version_option(version=__version__)
|
||||
@click.pass_context
|
||||
def cli(ctx):
|
||||
"""MockAPI - OpenAPI Mock Server Generator.
|
||||
|
||||
Generate functional mock API servers from OpenAPI 3.x specifications.
|
||||
"""
|
||||
ctx.ensure_object(dict)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument("spec_file", type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--format",
|
||||
"fmt",
|
||||
type=click.Choice(["yaml", "json"]),
|
||||
default=None,
|
||||
help="Force spec format (auto-detected if not specified)",
|
||||
)
|
||||
def validate(spec_file: str, fmt: Optional[str]):
|
||||
"""Validate an OpenAPI specification file.
|
||||
|
||||
SPEC_FILE: Path to the OpenAPI spec file (YAML or JSON)
|
||||
"""
|
||||
try:
|
||||
loader = SpecLoader(spec_file, fmt)
|
||||
spec = loader.load()
|
||||
|
||||
validator = OpenAPIValidator(spec)
|
||||
errors = validator.validate()
|
||||
|
||||
if errors:
|
||||
click.echo("Validation failed:", err=True)
|
||||
for error in errors:
|
||||
click.echo(f" - {error}", err=True)
|
||||
sys.exit(1)
|
||||
else:
|
||||
click.echo("✓ Specification is valid!")
|
||||
click.echo(f" Paths: {len(spec.get('paths', {}))}")
|
||||
click.echo(f" Schemas: {len(spec.get('components', {}).get('schemas', {}))}")
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument("spec_file", type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--port",
|
||||
"-p",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Port to run the mock server on (default: from config or 8080)",
|
||||
)
|
||||
@click.option(
|
||||
"--host",
|
||||
"-h",
|
||||
default=None,
|
||||
help="Host to bind to (default: from config or 0.0.0.0)",
|
||||
)
|
||||
@click.option(
|
||||
"--delay",
|
||||
"-d",
|
||||
type=int,
|
||||
default=None,
|
||||
help="Fixed response delay in milliseconds",
|
||||
)
|
||||
@click.option(
|
||||
"--random-delay",
|
||||
is_flag=True,
|
||||
default=None,
|
||||
help="Use random delays instead of fixed",
|
||||
)
|
||||
@click.option(
|
||||
"--config",
|
||||
"-c",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Path to mockapi.yaml configuration file",
|
||||
)
|
||||
@click.option(
|
||||
"--watch",
|
||||
"-w",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Enable hot-reload on spec file changes",
|
||||
)
|
||||
@click.option(
|
||||
"--verbose",
|
||||
"-v",
|
||||
is_flag=True,
|
||||
default=False,
|
||||
help="Enable verbose output",
|
||||
)
|
||||
def start(
|
||||
spec_file: str,
|
||||
port: Optional[int],
|
||||
host: Optional[str],
|
||||
delay: Optional[int],
|
||||
random_delay: Optional[bool],
|
||||
config: Optional[str],
|
||||
watch: bool,
|
||||
verbose: bool,
|
||||
):
|
||||
"""Start a mock API server from an OpenAPI specification.
|
||||
|
||||
SPEC_FILE: Path to the OpenAPI spec file (YAML or JSON)
|
||||
"""
|
||||
try:
|
||||
cfg = Config.load(config_path=config)
|
||||
|
||||
if port is not None:
|
||||
cfg.port = port
|
||||
if host is not None:
|
||||
cfg.host = host
|
||||
if delay is not None:
|
||||
cfg.delay = delay
|
||||
if random_delay is not None:
|
||||
cfg.random_delay = random_delay
|
||||
|
||||
loader = SpecLoader(spec_file)
|
||||
spec = loader.load()
|
||||
|
||||
validator = OpenAPIValidator(spec)
|
||||
errors = validator.validate()
|
||||
if errors:
|
||||
click.echo("Specification validation failed:", err=True)
|
||||
for error in errors:
|
||||
click.echo(f" - {error}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
if verbose:
|
||||
click.echo(f"Starting mock server on {cfg.host}:{cfg.port}")
|
||||
click.echo(f"Spec file: {spec_file}")
|
||||
|
||||
generator = MockServerGenerator(spec, cfg)
|
||||
app = generator.generate()
|
||||
|
||||
if watch:
|
||||
reloader = HotReloader(spec_file, port=cfg.port, host=cfg.host)
|
||||
click.echo(f"Watching {spec_file} for changes...")
|
||||
reloader.start_watching()
|
||||
else:
|
||||
import uvicorn
|
||||
uvicorn.run(
|
||||
app,
|
||||
host=cfg.host,
|
||||
port=cfg.port,
|
||||
log_level="info" if verbose else "warning",
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
if verbose:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.argument("spec_file", type=click.Path(exists=True))
|
||||
@click.option(
|
||||
"--output",
|
||||
"-o",
|
||||
type=click.Path(),
|
||||
default=None,
|
||||
help="Output file path (default: stdout)",
|
||||
)
|
||||
def generate(spec_file: str, output: Optional[str]):
|
||||
"""Generate code/structure from an OpenAPI spec (dry-run mode).
|
||||
|
||||
SPEC_FILE: Path to the OpenAPI spec file (YAML or JSON)
|
||||
"""
|
||||
try:
|
||||
loader = SpecLoader(spec_file)
|
||||
spec = loader.load()
|
||||
|
||||
validator = OpenAPIValidator(spec)
|
||||
errors = validator.validate()
|
||||
|
||||
if errors:
|
||||
click.echo("Specification validation failed:", err=True)
|
||||
for error in errors:
|
||||
click.echo(f" - {error}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
paths = spec.get("paths", {})
|
||||
schemas = spec.get("components", {}).get("schemas", {})
|
||||
|
||||
info = spec.get("info", {})
|
||||
title = info.get("title", "API")
|
||||
version = info.get("version", "1.0.0")
|
||||
|
||||
output_lines = [
|
||||
f"# OpenAPI Spec: {title} v{version}",
|
||||
f"# Endpoints: {len(paths)}",
|
||||
f"# Schemas: {len(schemas)}",
|
||||
"",
|
||||
"## Paths",
|
||||
]
|
||||
|
||||
for path, path_item in paths.items():
|
||||
for method, operation in path_item.items():
|
||||
if method in ["get", "post", "put", "delete", "patch", "options", "head"]:
|
||||
op_id = operation.get("operationId", f"{method}_{path}")
|
||||
summary = operation.get("summary", "")
|
||||
output_lines.append(f" {method.upper():7} {path} -> {op_id}")
|
||||
if summary:
|
||||
output_lines.append(f" {summary}")
|
||||
|
||||
if output:
|
||||
with open(output, "w") as f:
|
||||
f.write("\n".join(output_lines))
|
||||
click.echo(f"Generated output written to {output}")
|
||||
else:
|
||||
click.echo("\n".join(output_lines))
|
||||
|
||||
except Exception as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
@cli.command()
|
||||
@click.option(
|
||||
"--config",
|
||||
"-c",
|
||||
type=click.Path(exists=True),
|
||||
default=None,
|
||||
help="Path to mockapi.yaml configuration file",
|
||||
)
|
||||
def show_config(config: Optional[str]):
|
||||
"""Show the current configuration (from file and defaults)."""
|
||||
try:
|
||||
cfg = Config.load(config_path=config)
|
||||
click.echo("Current MockAPI Configuration:")
|
||||
click.echo(f" Port: {cfg.port}")
|
||||
click.echo(f" Host: {cfg.host}")
|
||||
click.echo(f" Delay: {cfg.delay}ms")
|
||||
click.echo(f" Random Delay: {cfg.random_delay}")
|
||||
click.echo(f" Seed: {cfg.seed}")
|
||||
click.echo(f" Validate: {cfg.validate_requests}")
|
||||
except Exception as e:
|
||||
click.echo(f"Error: {e}", err=True)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli()
|
||||
Reference in New Issue
Block a user