433 lines
9.9 KiB
Python
433 lines
9.9 KiB
Python
"""
|
|
pyserve init - Initialize a new pyserve project
|
|
"""
|
|
|
|
from pathlib import Path
|
|
|
|
import click
|
|
|
|
TEMPLATES = {
|
|
"basic": {
|
|
"description": "Basic configuration with static files and routing",
|
|
"filename": "config.yaml",
|
|
},
|
|
"orchestration": {
|
|
"description": "Process orchestration with multiple ASGI/WSGI apps",
|
|
"filename": "config.yaml",
|
|
},
|
|
"asgi": {
|
|
"description": "ASGI mount configuration for in-process apps",
|
|
"filename": "config.yaml",
|
|
},
|
|
"full": {
|
|
"description": "Full configuration with all features",
|
|
"filename": "config.yaml",
|
|
},
|
|
}
|
|
|
|
BASIC_TEMPLATE = """\
|
|
# PyServe Configuration
|
|
# Generated by: pyserve init
|
|
|
|
http:
|
|
static_dir: ./static
|
|
templates_dir: ./templates
|
|
|
|
server:
|
|
host: 0.0.0.0
|
|
port: 8080
|
|
backlog: 100
|
|
proxy_timeout: 30.0
|
|
|
|
ssl:
|
|
enabled: false
|
|
cert_file: ./ssl/cert.pem
|
|
key_file: ./ssl/key.pem
|
|
|
|
logging:
|
|
level: INFO
|
|
console_output: true
|
|
format:
|
|
type: standard
|
|
use_colors: true
|
|
show_module: true
|
|
timestamp_format: "%Y-%m-%d %H:%M:%S"
|
|
console:
|
|
level: INFO
|
|
format:
|
|
type: standard
|
|
use_colors: true
|
|
files:
|
|
- path: ./logs/pyserve.log
|
|
level: INFO
|
|
format:
|
|
type: standard
|
|
use_colors: false
|
|
|
|
extensions:
|
|
- type: routing
|
|
config:
|
|
regex_locations:
|
|
# Health check endpoint
|
|
"=/health":
|
|
return: "200 OK"
|
|
content_type: "text/plain"
|
|
|
|
# Static files
|
|
"^/static/":
|
|
root: "./static"
|
|
strip_prefix: "/static"
|
|
|
|
# Default fallback
|
|
"__default__":
|
|
spa_fallback: true
|
|
root: "./static"
|
|
index_file: "index.html"
|
|
"""
|
|
|
|
ORCHESTRATION_TEMPLATE = """\
|
|
# PyServe Process Orchestration Configuration
|
|
# Generated by: pyserve init --template orchestration
|
|
#
|
|
# This configuration runs multiple ASGI/WSGI apps as isolated processes
|
|
# with automatic health monitoring and restart.
|
|
|
|
server:
|
|
host: 0.0.0.0
|
|
port: 8080
|
|
backlog: 2048
|
|
proxy_timeout: 60.0
|
|
|
|
logging:
|
|
level: INFO
|
|
console_output: true
|
|
format:
|
|
type: standard
|
|
use_colors: true
|
|
files:
|
|
- path: ./logs/pyserve.log
|
|
level: DEBUG
|
|
format:
|
|
type: standard
|
|
use_colors: false
|
|
|
|
extensions:
|
|
# Process Orchestration - runs each app in its own process
|
|
- type: process_orchestration
|
|
config:
|
|
port_range: [9000, 9999]
|
|
health_check_enabled: true
|
|
proxy_timeout: 60.0
|
|
|
|
apps:
|
|
# Example: FastAPI application
|
|
- name: api
|
|
path: /api
|
|
app_path: myapp.api:app
|
|
module_path: "."
|
|
workers: 2
|
|
health_check_path: /health
|
|
health_check_interval: 10.0
|
|
health_check_timeout: 5.0
|
|
health_check_retries: 3
|
|
max_restart_count: 5
|
|
restart_delay: 1.0
|
|
strip_path: true
|
|
env:
|
|
APP_ENV: "production"
|
|
|
|
# Example: Flask application (WSGI)
|
|
# - name: admin
|
|
# path: /admin
|
|
# app_path: myapp.admin:app
|
|
# app_type: wsgi
|
|
# module_path: "."
|
|
# workers: 1
|
|
# health_check_path: /health
|
|
# strip_path: true
|
|
|
|
# Static files routing
|
|
- type: routing
|
|
config:
|
|
regex_locations:
|
|
"=/health":
|
|
return: "200 OK"
|
|
content_type: "text/plain"
|
|
|
|
"^/static/":
|
|
root: "./static"
|
|
strip_prefix: "/static"
|
|
"""
|
|
|
|
ASGI_TEMPLATE = """\
|
|
# PyServe ASGI Mount Configuration
|
|
# Generated by: pyserve init --template asgi
|
|
#
|
|
# This configuration mounts ASGI apps in-process (like ASGI Lifespan).
|
|
# More efficient but apps share the same process.
|
|
|
|
server:
|
|
host: 0.0.0.0
|
|
port: 8080
|
|
backlog: 100
|
|
proxy_timeout: 30.0
|
|
|
|
logging:
|
|
level: INFO
|
|
console_output: true
|
|
format:
|
|
type: standard
|
|
use_colors: true
|
|
files:
|
|
- path: ./logs/pyserve.log
|
|
level: DEBUG
|
|
|
|
extensions:
|
|
- type: asgi_mount
|
|
config:
|
|
mounts:
|
|
# FastAPI app mounted at /api
|
|
- path: /api
|
|
app: myapp.api:app
|
|
# factory: false # Set to true if app is a factory function
|
|
|
|
# Starlette app mounted at /web
|
|
# - path: /web
|
|
# app: myapp.web:app
|
|
|
|
- type: routing
|
|
config:
|
|
regex_locations:
|
|
"=/health":
|
|
return: "200 OK"
|
|
content_type: "text/plain"
|
|
|
|
"^/static/":
|
|
root: "./static"
|
|
strip_prefix: "/static"
|
|
|
|
"__default__":
|
|
spa_fallback: true
|
|
root: "./static"
|
|
index_file: "index.html"
|
|
"""
|
|
|
|
FULL_TEMPLATE = """\
|
|
# PyServe Full Configuration
|
|
# Generated by: pyserve init --template full
|
|
#
|
|
# Comprehensive configuration showcasing all PyServe features.
|
|
|
|
http:
|
|
static_dir: ./static
|
|
templates_dir: ./templates
|
|
|
|
server:
|
|
host: 0.0.0.0
|
|
port: 8080
|
|
backlog: 2048
|
|
default_root: false
|
|
proxy_timeout: 60.0
|
|
redirect_instructions:
|
|
"/old-path": "/new-path"
|
|
|
|
ssl:
|
|
enabled: false
|
|
cert_file: ./ssl/cert.pem
|
|
key_file: ./ssl/key.pem
|
|
|
|
logging:
|
|
level: INFO
|
|
console_output: true
|
|
format:
|
|
type: standard
|
|
use_colors: true
|
|
show_module: true
|
|
timestamp_format: "%Y-%m-%d %H:%M:%S"
|
|
console:
|
|
level: DEBUG
|
|
format:
|
|
type: standard
|
|
use_colors: true
|
|
files:
|
|
# Main log file
|
|
- path: ./logs/pyserve.log
|
|
level: DEBUG
|
|
format:
|
|
type: standard
|
|
use_colors: false
|
|
|
|
# JSON logs for log aggregation
|
|
- path: ./logs/pyserve.json
|
|
level: INFO
|
|
format:
|
|
type: json
|
|
|
|
# Access logs
|
|
- path: ./logs/access.log
|
|
level: INFO
|
|
loggers: ["pyserve.access"]
|
|
max_bytes: 10485760 # 10MB
|
|
backup_count: 10
|
|
|
|
extensions:
|
|
# Process Orchestration for background services
|
|
- type: process_orchestration
|
|
config:
|
|
port_range: [9000, 9999]
|
|
health_check_enabled: true
|
|
proxy_timeout: 60.0
|
|
|
|
apps:
|
|
- name: api
|
|
path: /api
|
|
app_path: myapp.api:app
|
|
module_path: "."
|
|
workers: 2
|
|
health_check_path: /health
|
|
strip_path: true
|
|
env:
|
|
APP_ENV: "production"
|
|
|
|
# Advanced routing with regex
|
|
- type: routing
|
|
config:
|
|
regex_locations:
|
|
# API versioning
|
|
"~^/api/v(?P<version>\\\\d+)/":
|
|
proxy_pass: "http://localhost:9001"
|
|
headers:
|
|
- "API-Version: {version}"
|
|
- "X-Forwarded-For: $remote_addr"
|
|
|
|
# Static files with caching
|
|
"~*\\\\.(js|css|png|jpg|gif|ico|svg|woff2?)$":
|
|
root: "./static"
|
|
cache_control: "public, max-age=31536000"
|
|
headers:
|
|
- "Access-Control-Allow-Origin: *"
|
|
|
|
# Health check
|
|
"=/health":
|
|
return: "200 OK"
|
|
content_type: "text/plain"
|
|
|
|
# Static files
|
|
"^/static/":
|
|
root: "./static"
|
|
strip_prefix: "/static"
|
|
|
|
# SPA fallback
|
|
"__default__":
|
|
spa_fallback: true
|
|
root: "./static"
|
|
index_file: "index.html"
|
|
"""
|
|
|
|
|
|
def get_template_content(template: str) -> str:
|
|
templates = {
|
|
"basic": BASIC_TEMPLATE,
|
|
"orchestration": ORCHESTRATION_TEMPLATE,
|
|
"asgi": ASGI_TEMPLATE,
|
|
"full": FULL_TEMPLATE,
|
|
}
|
|
return templates.get(template, BASIC_TEMPLATE)
|
|
|
|
|
|
@click.command("init")
|
|
@click.option(
|
|
"-t",
|
|
"--template",
|
|
"template",
|
|
type=click.Choice(list(TEMPLATES.keys())),
|
|
default="basic",
|
|
help="Configuration template to use",
|
|
)
|
|
@click.option(
|
|
"-o",
|
|
"--output",
|
|
"output_file",
|
|
default="config.yaml",
|
|
help="Output file path (default: config.yaml)",
|
|
)
|
|
@click.option(
|
|
"-f",
|
|
"--force",
|
|
is_flag=True,
|
|
help="Overwrite existing configuration",
|
|
)
|
|
@click.option(
|
|
"--list-templates",
|
|
is_flag=True,
|
|
help="List available templates",
|
|
)
|
|
@click.pass_context
|
|
def init_cmd(
|
|
ctx,
|
|
template: str,
|
|
output_file: str,
|
|
force: bool,
|
|
list_templates: bool,
|
|
):
|
|
"""
|
|
Initialize a new pyserve project.
|
|
|
|
Creates a configuration file with sensible defaults and directory structure.
|
|
|
|
\b
|
|
Examples:
|
|
pyserve init # Basic configuration
|
|
pyserve init -t orchestration # Process orchestration setup
|
|
pyserve init -t asgi # ASGI mount setup
|
|
pyserve init -t full # All features
|
|
pyserve init -o production.yaml # Custom output file
|
|
"""
|
|
from ..output import console, print_info, print_success, print_warning
|
|
|
|
if list_templates:
|
|
console.print("\n[bold]Available Templates:[/bold]\n")
|
|
for name, info in TEMPLATES.items():
|
|
console.print(f" [cyan]{name:15}[/cyan] - {info['description']}")
|
|
console.print()
|
|
return
|
|
|
|
output_path = Path(output_file)
|
|
|
|
if output_path.exists() and not force:
|
|
print_warning(f"Configuration file '{output_file}' already exists.")
|
|
if not click.confirm("Do you want to overwrite it?"):
|
|
raise click.Abort()
|
|
|
|
dirs_to_create = ["static", "templates", "logs"]
|
|
if template == "orchestration":
|
|
dirs_to_create.append("apps")
|
|
|
|
for dir_name in dirs_to_create:
|
|
dir_path = Path(dir_name)
|
|
if not dir_path.exists():
|
|
dir_path.mkdir(parents=True)
|
|
print_info(f"Created directory: {dir_name}/")
|
|
|
|
state_dir = Path(".pyserve")
|
|
if not state_dir.exists():
|
|
state_dir.mkdir()
|
|
print_info("Created directory: .pyserve/")
|
|
|
|
content = get_template_content(template)
|
|
output_path.write_text(content)
|
|
|
|
print_success(f"Created configuration file: {output_file}")
|
|
print_info(f"Template: {template}")
|
|
|
|
gitignore_path = Path(".pyserve/.gitignore")
|
|
if not gitignore_path.exists():
|
|
gitignore_path.write_text("*\n!.gitignore\n")
|
|
|
|
console.print()
|
|
console.print("[bold]Next steps:[/bold]")
|
|
console.print(f" 1. Edit [cyan]{output_file}[/cyan] to configure your services")
|
|
console.print(" 2. Run [cyan]pyserve config validate[/cyan] to check configuration")
|
|
console.print(" 3. Run [cyan]pyserve up[/cyan] to start services")
|
|
console.print()
|