Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ src/lazy_ninja.egg-info
/dist
/doc/_build
venv
.coverage
.coverage
.venv
115 changes: 112 additions & 3 deletions docs/docs/en/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ pip install lazy-ninja

----------


## Quick Start

### 1. Create a Model
Expand Down Expand Up @@ -91,7 +92,7 @@ python manage.py runserver

---------------

## Instant API Endpoints
### Instant API Endpoints

Your API is now live at `http://localhost:8000/api` with these endpoints:

Expand All @@ -105,15 +106,123 @@ Your API is now live at `http://localhost:8000/api` with these endpoints:

----------

## Interactive Documentation
### Interactive Documentation

Access auto-generated documentation through:

### Swagger UI
#### Swagger UI

`http://localhost:8000/api/docs`

![Swagger UI](../images/sample/swagger.png)

----------

## CLI Usage

Lazy Ninja ships with a **Command Line Interface (CLI)** to speed up common tasks:

- Scaffold a new Django project with Lazy Ninja preconfigured.

- Generate OpenAPI clients/SDKs for multiple languages.


It removes boilerplate and keeps backend ↔ frontend integration consistent.

----------

### Overview

- `lazy-ninja init` (alias `startproject`) — create a Django project scaffold (`api.py`, `urls.py` ready).

- `lazy-ninja generate-client` — generate OpenAPI clients/SDKs (TypeScript, Dart, Python, Java, Go, C#, Ruby, Swift, etc.).

- CLI auto-exports your schema from Django + Ninja (no server run needed), or you can provide `--schema`.


----------

### Prerequisites

- **`generate-client`** needs `openapi-generator-cli`.

- Install with `pip install lazy-ninja[standard]` (includes JDK via `jdk4py`).

- If you already have Java: `pip install lazy-ninja[no-jdk]`.

- For `typescript-types`: requires **Node.js/npm** (uses `npx openapi-typescript`).

- Offline mode: pre-generate schema and use `--schema openapi.json`.


----------

### `init` — Project scaffold
```bash
lazy-ninja init myproject --title "My API"
```

Creates a Django project, adds `api.py` + `urls.py`, and comments in `settings.py` showing how to enable the API.

----------

### `generate-client` — Client/SDK generation

`lazy-ninja generate-client <language> \
--settings myproject.settings \
--api-module myproject.api \
--output ./clients/<target>`

Options:

- `<language>` — generator name (`typescript-axios`, `python`, `dart`, etc.).

- `--schema` — skip Django import, use a pre-generated OpenAPI JSON.

- `--api-var` — defaults to `api`.


Examples:
```bash
# TypeScript axios client
lazy-ninja generate-client typescript-axios --settings myproject.settings --output ./clients/ts-axios

# Dart client
lazy-ninja generate-client dart --settings myproject.settings --output ./clients/dart

# From pre-generated schema
lazy-ninja generate-client python --schema ./openapi.json --output ./clients/python
```

----------

### Supported generators

- `typescript-types` (via `npx openapi-typescript`)

- `typescript-axios`, `typescript-fetch`

- `dart`, `dart-dio`

- `python`

- `java`, `kotlin`, `go`, `csharp`, `ruby`, `swift5`
_(see `GENERATOR_CONFIG` for full list)_


----------

### Benefits

- **Zero-boilerplate** — scaffold project + API with one command.

- **Automatic schema export** — generate clients without running the server.

- **Multi-target** — TypeScript clients for frontend, SDKs for backend.

- **Offline ready** — use `--schema` for CI/CD or locked-down environments.


----------

## Advanced Example
Expand Down
2 changes: 1 addition & 1 deletion docs/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ plugins:

nav:
- Home: index.md
- Features: features.md
# - Features: features.md

- "API Reference": api-reference.md
- Contributing: contributing.md
10 changes: 7 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ dependencies = [
"django-ninja>=0.22",
"pydantic>=2.0",
"inflect>=7.5",
"openapi-generator-cli>=7.14.0"
]

classifiers = [
Expand All @@ -32,10 +31,15 @@ classifiers = [
]

[project.optional-dependencies]
lazy-jdk = [
standard = [
"openapi-generator-cli[jdk4py]>=7.14.0"
]

no-jdk= [
"openapi-generator-cli>=7.14.0"
]


[tool.setuptools.packages.find]
where = ["src"]
exclude = ["lazy_ninja.tests*"]
Expand All @@ -45,5 +49,5 @@ version_scheme = "python-simplified-semver"
local_scheme = "no-local-version"

[project.scripts]
lazy-ninja = "lazy_ninja.cli.client_generator:main"
lazy-ninja = "lazy_ninja.cli.main:main"

101 changes: 33 additions & 68 deletions src/lazy_ninja/cli/client_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,74 +143,39 @@ def generate_client(schema_path: Path, language: str, output: str):
sys.exit(proc.returncode)
print(f"[LazyNinja] ✅ Client ({language}) generated at {output}")

def handle_generate_client(args):
"""
args: namespace from argparse with attributes:
- language, output, settings, api_module, schema (optional), api_var
"""
schema_file = Path(args.schema) if getattr(args, "schema", None) else Path(".lazy_ninja_openapi.json")

def main():
parser = argparse.ArgumentParser(
prog="lazy-ninja",
description=(
"🌀 Lazy Ninja CLI\n\n"
"Generate client code and SDKs from your Django + Ninja API schema.\n\n"
"Supports generating frontend clients (e.g., TypeScript for React) as well as backend SDKs\n"
"for server-to-server communication or internal services.\n\n"
"Example usage:\n"
" lazy-ninja generate-client typescript \\\n"
" --settings myproject.settings \\\n"
" --api-module myproject.api \\\n"
" --output ./client.ts"
),
epilog="🐛 Report issues at: https://github.com/AghastyGD/lazy-ninja/issues",
formatter_class=argparse.RawDescriptionHelpFormatter
)
if getattr(args, "schema", None):
if not schema_file.exists():
print(f"[LazyNinja] ❌ Schema file not found: {schema_file}")
sys.exit(1)
else:
try:
setup_django(args.settings)
except Exception as e:
print("[LazyNinja] ❌ Failed to setup Django. Make sure your settings are importable.")
print("Error:", e)
sys.exit(1)

sub = parser.add_subparsers(dest="cmd")
try:
dump_openapi(args.api_module, args.api_var, schema_file)
except ModuleNotFoundError as e:
print("[LazyNinja] ❌ Failed to import module while dumping OpenAPI schema.")
print("Missing module:", e.name)
print("Consider generating a schema file with your project dependencies installed, then pass --schema path/to/schema.json")
sys.exit(1)
except Exception as e:
print("[LazyNinja] ❌ Failed to generate schema.")
print("Error:", e)
sys.exit(1)

gen = sub.add_parser(
"generate-client",
help="Generate client code from OpenAPI schema",
description=(
"Generate client code from the OpenAPI schema exposed by your Django Ninja API.\n\n"
"Supports multiple languages. You must provide your Django settings module and\n"
"the path to the module where your `api = NinjaAPI()` instance is defined."
),
formatter_class=argparse.RawDescriptionHelpFormatter
)

gen.add_argument(
"language",
choices=list(GENERATOR_CONFIG.keys()),
help="Target language for client code (e.g. typescript, python)"
)
gen.add_argument(
"--settings",
required=True,
help="Django settings module (e.g. myproject.settings)"
)
gen.add_argument(
"--api-module",
default="settings.api",
help="Module path where your `api = NinjaAPI()` is defined (default: settings.api)"
)
gen.add_argument(
"--output",
default="./client",
help="Output file or folder (e.g. ./client.ts)"
)

args = parser.parse_args()

if not args.cmd:
parser.print_help()
sys.exit(1)

setup_django(args.settings)
schema_file = Path(".lazy_ninja_openapi.json")
dump_openapi(args.api_module, "api", schema_file)
generate_client(schema_file, args.language, args.output)
schema_file.unlink(missing_ok=True)


if __name__ == "__main__":
main()



try:
generate_client(schema_file, args.language, args.output)
finally:
if not getattr(args, "schema", None) and schema_file.exists():
schema_file.unlink(missing_ok=True)
40 changes: 40 additions & 0 deletions src/lazy_ninja/cli/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from __future__ import annotations
import argparse
from pathlib import Path
from . import client_generator, startproject

def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="lazy-ninja",
description="🌀 Lazy Ninja CLI - tools for generating clients and projects.",
formatter_class=argparse.RawDescriptionHelpFormatter
)
sub = parser.add_subparsers(dest="cmd")

gen = sub.add_parser("generate-client", help="Generate client code from OpenAPI schema")
gen.add_argument("language", choices=list(client_generator.GENERATOR_CONFIG.keys()))
gen.add_argument("--output", default="./client", help="Output dir or file")
gen.add_argument("--settings", help="Django settings module (e.g. myproject.settings). Required unless --schema is provided.")
gen.add_argument("--api-module", default="settings.api", help="Module path where `api = NinjaAPI()` is defined")
gen.add_argument("--api-var", default="api", help="Name of the Ninja API variable in module (default: api)")
gen.add_argument("--schema", type=Path, help="Path to pre-generated OpenAPI JSON (skip Django setup)")

proj = sub.add_parser("init", help="Create a Django project scaffold preconfigured for Lazy Ninja")
proj.add_argument("name", help="Project name")
proj.add_argument("directory", nargs="?", default=None, help="Optional target directory (same semantics as django-admin)")
proj.add_argument("--title", nargs="?", default=None, help="Optional API title")

return parser

def main(argv: list[str] | None = None):
parser = build_parser()
args = parser.parse_args(argv)

if args.cmd == "generate-client":
if args.schema is None and not args.settings:
parser.error("either --schema or --settings must be provided for generate-client")
client_generator.handle_generate_client(args)
elif args.cmd == "init":
startproject.startproject_command(args.name, args.directory, args.title)
else:
parser.print_help()
Loading