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