diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml index 3e67bd6..f4a9f8b 100644 --- a/.github/workflows/welcome.yml +++ b/.github/workflows/welcome.yml @@ -22,7 +22,7 @@ jobs: const marker = ''; const message = `${marker} -👋 # Thanks for your contribution, @${sender}! +👋 Thanks for your contribution, @${sender}! Please make sure: - Code is clean and formatted diff --git a/docs/en/cli.md b/docs/en/cli.md index 1b90f77..bac45a3 100644 --- a/docs/en/cli.md +++ b/docs/en/cli.md @@ -267,6 +267,75 @@ clear # clear screen exit # exit REPL ``` +## Running Application Files + +FastHTTP CLI provides two commands to run your application files directly. + +### Run Mode — Execute Requests + +Run your FastHTTP app in request mode. Executes all registered HTTP requests and displays results: + +```bash +fasthttp run main.py +``` + +With tag filtering: + +```bash +fasthttp run main.py --tags api,users +``` + +Enable debug mode: + +```bash +fasthttp run main.py --debug +``` + +Options: +- `-t, --tags` — Run only routes with specific tags (comma-separated) +- `-d, --debug` — Enable debug mode + +### Dev Mode — Development Server + +Run your FastHTTP app with an interactive Swagger UI for testing requests: + +```bash +fasthttp dev main.py +``` + +With custom host and port: + +```bash +fasthttp dev main.py --host 0.0.0.0 --port 3000 +``` + +With base URL for documentation: + +```bash +fasthttp dev main.py --base-url /api +``` + +Enable debug mode: + +```bash +fasthttp dev main.py --debug +``` + +Options: +- `-h, --host` — Host to bind the server to (default: 127.0.0.1) +- `-p, --port` — Port to bind the server to (default: 8000) +- `-b, --base-url` — Base URL prefix for documentation endpoints +- `-d, --debug` — Enable debug mode + +Example output: + +``` +▲ FastHTTP dev server +➜ Server: http://127.0.0.1:8000 +➜ Docs: http://127.0.0.1:8000/docs +───────────────────────────────── +``` + ## See Also - [Quick Start](quick-start.md) — basics diff --git a/docs/ru/cli.md b/docs/ru/cli.md index 7b6494b..1cc687d 100644 --- a/docs/ru/cli.md +++ b/docs/ru/cli.md @@ -103,17 +103,9 @@ fasthttp get https://api.example.com/data --debug - JSON/data тело - Заголовки ответа -## Формат вывода - -Второй аргумент после URL определяет что вывести: - -### status — только статус (по умолчанию) - ### Прокси (-p, --proxy) ```bash -fasthttp get https://api.example.com/data status -# 200 # HTTP прокси fasthttp get https://api.example.com/data -p "http://proxy.example.com:8080" @@ -127,8 +119,6 @@ fasthttp get https://api.example.com/data -p "socks5://proxy.example.com:1080" fasthttp get https://api.example.com/data -p "http://user:password@proxy.example.com:8080" ``` -### json — JSON тело ответа - ## Формат вывода Второй аргумент после URL определяет что вывести: @@ -136,62 +126,34 @@ fasthttp get https://api.example.com/data -p "http://user:password@proxy.example ### status — только статус (по умолчанию) ```bash -fasthttp get https://api.example.com/data json -# {"id": 1, "name": "John"} fasthttp get https://api.example.com/data status # 200 ``` -### text — текст ответа - ### json — JSON тело ответа ```bash -fasthttp get https://api.example.com/data text -# ... fasthttp get https://api.example.com/data json # {"id": 1, "name": "John"} ``` -### headers — заголовки ответа - ### text — текст ответа ```bash -fasthttp get https://api.example.com/data headers -# {"Content-Type": "application/json", "Date": "..."} fasthttp get https://api.example.com/data text # ... ``` -### all — всё вместе - ### headers — заголовки ответа ```bash -fasthttp get https://api.example.com/data all -# Status: 200 -# Elapsed: 234.56ms -# Headers: {...} -# Body: {...} fasthttp get https://api.example.com/data headers # {"Content-Type": "application/json", "Date": "..."} ``` -## Примеры - -### Простой GET - ### all — всё вместе ```bash -$ fasthttp get https://jsonplaceholder.typicode.com/posts/1 json -{ - "userId": 1, - "id": 1, - "title": "sunt aut facere repellat provident occaecati", - "body": "..." -} fasthttp get https://api.example.com/data all # Status: 200 # Elapsed: 234.56ms @@ -199,41 +161,26 @@ fasthttp get https://api.example.com/data all # Body: {...} ``` -### POST с JSON - ## Примеры ### Простой GET ```bash -$ fasthttp post https://jsonplaceholder.typicode.com/posts \ - --json '{"title": "foo", "body": "bar", "userId": 1}' json $ fasthttp get https://jsonplaceholder.typicode.com/posts/1 json { - "title": "foo", - "body": "bar", "userId": 1, - "id": 101 "id": 1, "title": "sunt aut facere repellat provident occaecati", "body": "..." } ``` -### С заголовками - ### POST с JSON ```bash -$ fasthttp get https://httpbin.org/headers \ - -H "Authorization: Bearer test-token" json $ fasthttp post https://jsonplaceholder.typicode.com/posts \ --json '{"title": "foo", "body": "bar", "userId": 1}' json { - "headers": { - "Authorization": "Bearer test-token", - "Host": "httpbin.org" - } "title": "foo", "body": "bar", "userId": 1, @@ -241,17 +188,9 @@ $ fasthttp post https://jsonplaceholder.typicode.com/posts \ } ``` -### С отладкой - ### С заголовками ```bash -$ fasthttp get https://httpbin.org/get --debug -ℹ → GET https://httpbin.org/get -✔ HTTP 200 in 234.56ms -ℹ ← Response headers: -ℹ Content-Type: application/json -ℹ Date: Mon, 15 Jan 2025 10:30:00 GMT $ fasthttp get https://httpbin.org/headers \ -H "Authorization: Bearer test-token" json { @@ -262,14 +201,9 @@ $ fasthttp get https://httpbin.org/headers \ } ``` -### Проверить статус API - ### С отладкой ```bash -$ fasthttp get https://api.example.com/health -✔ HTTP 200 in 45.23ms -200 $ fasthttp get https://httpbin.org/get --debug ℹ → GET https://httpbin.org/get ✔ HTTP 200 in 234.56ms @@ -278,25 +212,130 @@ $ fasthttp get https://httpbin.org/get --debug ℹ Date: Mon, 15 Jan 2025 10:30:00 GMT ``` -## Справка - ### Проверить статус API ```bash -fasthttp --help -fasthttp get --help -fasthttp post --help $ fasthttp get https://api.example.com/health ✔ HTTP 200 in 45.23ms 200 ``` +## Интерактивный REPL + +Запуск интерактивного режима: + +```bash +fasthttp repl +``` + +С прокси: + +```bash +fasthttp repl -p "http://proxy:8080" +``` + +### Команды REPL + +```bash +# Выполнение запросов +get https://api.example.com/data +post https://api.example.com/users -j '{"name": "John"}' +g https://api.example.com/data # сокращение для GET +p https://api.example.com/users # сокращение для POST + +# Опции +-H "Key:Value" # заголовки +-j '{"json": 1}' # JSON тело +-d "data" # form данные +-t 30 # таймаут +-o json # формат вывода +-p "proxy" # URL прокси + +# Специальные команды +help # показать справку +history # показать историю команд +last # показать последний ответ +clear # очистить экран +exit # выйти из REPL +``` + +## Запуск файлов приложения + +FastHTTP CLI предоставляет две команды для запуска файлов приложения напрямую. + +### Режим run — Выполнение запросов + +Запустите ваше FastHTTP приложение в режиме выполнения запросов: + +```bash +fasthttp run main.py +``` + +С фильтрацией по тегам: + +```bash +fasthttp run main.py --tags api,users +``` + +Включить режим отладки: + +```bash +fasthttp run main.py --debug +``` + +Опции: +- `-t, --tags` — Запустить только маршруты с определёнными тегами (через запятую) +- `-d, --debug` — Включить режим отладки + +### Режим dev — Сервер разработки + +Запустите ваше FastHTTP приложение с интерактивным Swagger UI для тестирования запросов: + +```bash +fasthttp dev main.py +``` + +С кастомным хостом и портом: + +```bash +fasthttp dev main.py --host 0.0.0.0 --port 3000 +``` + +С базовым URL для документации: + +```bash +fasthttp dev main.py --base-url /api +``` + +Включить режим отладки: + +```bash +fasthttp dev main.py --debug +``` + +Опции: +- `-h, --host` — Хост для сервера (по умолчанию: 127.0.0.1) +- `-p, --port` — Порт для сервера (по умолчанию: 8000) +- `-b, --base-url` — Базовый URL для эндпоинтов документации +- `-d, --debug` — Включить режим отладки + +Пример вывода: + +``` +▲ FastHTTP dev server +➜ Server: http://127.0.0.1:8000 +➜ Docs: http://127.0.0.1:8000/docs +───────────────────────────────── +``` + ## Справка ```bash fasthttp --help fasthttp get --help fasthttp post --help +fasthttp run --help +fasthttp dev --help ``` ## Смотрите также diff --git a/examples/basic/test_delete.py b/examples/basic/test_delete.py index 9659f07..8d2c72a 100644 --- a/examples/basic/test_delete.py +++ b/examples/basic/test_delete.py @@ -9,5 +9,4 @@ async def test_delete(resp: Response) -> int: return resp.status -if __name__ == "__main__": - app.run() + diff --git a/fasthttp/cli/main.py b/fasthttp/cli/main.py index 7f284f2..fad47e9 100644 --- a/fasthttp/cli/main.py +++ b/fasthttp/cli/main.py @@ -3,6 +3,7 @@ from fasthttp.__meta__ import __version__ from fasthttp.cli.commands import app as commands_app from fasthttp.cli.output import formatter +from fasthttp.cli.run import run_app app = typer.Typer( name="fasthttp", @@ -38,5 +39,6 @@ def callback(ctx: typer.Context) -> None: app.add_typer(commands_app, name="") +app.add_typer(run_app, name="") __all__ = ("app",) diff --git a/fasthttp/cli/run.py b/fasthttp/cli/run.py new file mode 100644 index 0000000..46f63c5 --- /dev/null +++ b/fasthttp/cli/run.py @@ -0,0 +1,184 @@ +from pathlib import Path +from typing import Any + +import typer + +from fasthttp.cli.output import formatter + +run_app = typer.Typer(help="Run FastHTTP application from file") + + +def _load_app_from_file(file_path: Path) -> Any: + """ + Load FastHTTP app instance from Python file. + + Args: + file_path: Path to the Python file containing the app. + + Returns: + FastHTTP app instance. + + Raises: + typer.Exit: If app cannot be loaded. + """ + import sys + from importlib.util import module_from_spec, spec_from_file_location + + if not file_path.exists(): + formatter.error(f"File not found: {file_path}") + raise typer.Exit(1) + + if file_path.suffix != ".py": + formatter.error(f"Expected Python file, got: {file_path.suffix}") + raise typer.Exit(1) + + module_name = file_path.stem + spec = spec_from_file_location(module_name, file_path) + + if spec is None or spec.loader is None: + formatter.error(f"Cannot load module from: {file_path}") + raise typer.Exit(1) + + module = module_from_spec(spec) + sys.modules[module_name] = module + + try: + spec.loader.exec_module(module) + except Exception as e: + formatter.error(f"Failed to execute {file_path.name}: {e}") + raise typer.Exit(1) + + app = None + for name in dir(module): + obj = getattr(module, name) + from fasthttp import FastHTTP + + if isinstance(obj, FastHTTP): + app = obj + break + + if app is None: + formatter.error( + f"No FastHTTP app found in {file_path.name}. " + "Make sure you have 'app = FastHTTP(...)' in your file." + ) + raise typer.Exit(1) + + return app + + +@run_app.command(name="run") +def run_command( + file: Path = typer.Argument( + ..., + help="Path to Python file with FastHTTP app", + exists=True, + file_okay=True, + dir_okay=False, + ), + tags: str | None = typer.Option( + None, + "-t", + "--tags", + help="Run only routes with specific tags (comma-separated)", + ), + debug: bool = typer.Option( + False, + "-d", + "--debug", + help="Enable debug mode", + ), +) -> None: + """ + Run FastHTTP app in request mode. + + Executes all registered HTTP requests and displays results. + """ + formatter.info(f"Loading app from: {file}") + + app = _load_app_from_file(file) + + if debug: + app.logger.setLevel("DEBUG") + app.debug = True + + tags_list = None + if tags: + tags_list = [tag.strip() for tag in tags.split(",")] + formatter.info(f"Running with tags: {tags_list}") + + print() + formatter.header("FastHTTP Runner") + formatter.key_value("Mode", "RUN") + formatter.key_value("File", file.name) + formatter.key_value("Routes", len(app.routes)) + if tags_list: + formatter.key_value("Filtered tags", ", ".join(tags_list)) + print() + + app.run(tags=tags_list) + + +@run_app.command(name="dev") +def dev_command( + file: Path = typer.Argument( + ..., + help="Path to Python file with FastHTTP app", + exists=True, + file_okay=True, + dir_okay=False, + ), + host: str = typer.Option( + "127.0.0.1", + "-h", + "--host", + help="Host to bind the server to", + ), + port: int = typer.Option( + 8000, + "-p", + "--port", + help="Port to bind the server to", + ), + base_url: str | None = typer.Option( + None, + "-b", + "--base-url", + help="Base URL prefix for documentation endpoints", + ), + debug: bool = typer.Option( + False, + "-d", + "--debug", + help="Enable debug mode", + ), +) -> None: + """ + Run FastHTTP app in development mode with Swagger UI. + + Starts an ASGI server with interactive documentation. + """ + formatter.info(f"Loading app from: {file}") + + app = _load_app_from_file(file) + + if debug: + app.logger.setLevel("DEBUG") + app.debug = True + + print() + formatter.header("FastHTTP Dev Server") + formatter.key_value("Mode", "DEV (Web UI)") + formatter.key_value("File", file.name) + formatter.key_value("Routes", len(app.routes)) + print() + + server_url = f"http://{host}:{port}" + docs_url = f"{server_url}{base_url or ''}/docs" + + print(f"\n\033[92m\033[1m▲ FastHTTP\033[0m \033[90mdev server\033[0m") + print(f"\033[94m➜\033[0m Server: \033[1m{server_url}\033[0m") + print(f"\033[94m➜\033[0m Docs: \033[1m{docs_url}\033[0m") + print(f"\033[90m─────────────────────────────────\033[0m\n") + + app.web_run(host=host, port=port, base_url=base_url)