Initial upload: mockapi - OpenAPI Mock Server Generator
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled

This commit is contained in:
2026-03-22 21:06:20 +00:00
parent 159402df2e
commit cae755068e

265
src/mockapi/cli/main.py Normal file
View 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()