diff --git a/README.md b/README.md index 68db5f7..70016ab 100644 --- a/README.md +++ b/README.md @@ -77,11 +77,30 @@ All documentation pages follow this structure: ## Code Highlighting -All pages include highlight.js for automatic syntax highlighting: +All pages use highlight.js for automatic syntax highlighting: -- CSS theme: `github-dark.min.css` (matches dark theme) -- Auto-initialization: `hljs.highlightAll()` -- Custom styles in `style.css` integrate with highlight.js +- **Theme**: `github-dark.min.css` (matches dark theme) +- **Auto-initialization**: `hljs.highlightAll()` runs on page load +- **Custom styles**: Enhanced color palette in `style.css` +- **Languages detected**: YAML, Bash, Python, Plaintext + +### Code Block Format + +All code blocks use semantic language classes: + +```html +
+server:
+ host: 0.0.0.0
+ port: 8080
+
+```
+
+Supported languages:
+- `language-yaml` — YAML configuration files
+- `language-bash` — Shell commands and scripts
+- `language-python` — Python code examples
+- `language-plaintext` — Plain text output
## Deployment
diff --git a/getting-started/installation.html b/getting-started/installation.html
index 4dc1f09..09bdaaa 100644
--- a/getting-started/installation.html
+++ b/getting-started/installation.html
@@ -32,32 +32,32 @@
Download the latest wheel file from Git Releases and install it:
-# Download the wheel file from releases -# Example: pyserve-0.7.0-py3-none-any.whl ++pip install pyserve-0.7.0-py3-none-any.whl# Download the wheel file from releases +# Example: pyserve-0.7.0-py3-none-any.whl -pip install pyserve-0.7.0-py3-none-any.whl
After installation, the pyserve command will be available in your terminal:
pyserve --version+
pyserve --version
For development or if you want the latest changes:
-# Clone the repository ++pip install dist/pyserve-*.whl# Clone the repository git clone https://github.com/ShiftyX1/PyServe.git cd PyServe -# Install with Poetry (recommended for development) +# Install with Poetry (recommended for development) make init -# Or build and install the package +# Or build and install the package make build -pip install dist/pyserve-*.whl
Check that pyserve is installed correctly:
-pyserve --version
-# Output: pyserve 0.7.0
+ pyserve --version
+# Output: pyserve 0.7.0
pyserve automatically installs the following dependencies:
diff --git a/getting-started/quickstart.html b/getting-started/quickstart.html index 422a00f..1e27a87 100644 --- a/getting-started/quickstart.html +++ b/getting-started/quickstart.html @@ -28,37 +28,37 @@Create a file named config.yaml in your project directory:
http: - static_dir: ./static - templates_dir: ./templates ++extensions: + - type: routing + config: + regex_locations: + "__default__": + root: "./static" + index_file: "index.html"http: + static_dir: ./static + templates_dir: ./templates -server: - host: 0.0.0.0 - port: 8080 +server: + host: 0.0.0.0 + port: 8080 -logging: - level: INFO - console_output: true +logging: + level: INFO + console_output: true -extensions: - - type: routing - config: - regex_locations: - "__default__": - root: "./static" - index_file: "index.html"
Create a static folder and add an index.html:
mkdir -p static -echo '<h1>Hello from pyserve!</h1>' > static/index.html+
mkdir -p static
+echo '<h1>Hello from pyserve!</h1>' > static/index.html
pyserve+
pyserve
You should see output like:
-Starting PyServe server on 0.0.0.0:8080+
Starting PyServe server on 0.0.0.0:8080
Navigate to http://localhost:8080 — you should see your page!
@@ -66,44 +66,44 @@ echo '<h1>Hello from pyserve!</h1>' > static/index.htmlOverride configuration via command line:
-# Use a different config file ++# Enable debug mode (verbose logging) +pyserve --debug# Use a different config file pyserve -c /path/to/config.yaml -# Override host and port +# Override host and port pyserve --host 127.0.0.1 --port 9000 -# Enable debug mode (verbose logging) -pyserve --debug
Serve a documentation directory with proper caching:
-http: - static_dir: ./docs ++ "__default__": + root: "./docs" + index_file: "index.html"http: + static_dir: ./docs -server: - host: 0.0.0.0 - port: 8000 +server: + host: 0.0.0.0 + port: 8000 -extensions: - - type: routing - config: - regex_locations: - "=/": - root: "./docs" - index_file: "index.html" +extensions: + - type: routing + config: + regex_locations: + "=/": + root: "./docs" + index_file: "index.html" - "~*\\.(css|js)$": - root: "./docs" - cache_control: "public, max-age=3600" + "~*\\.(css|js)$": + root: "./docs" + cache_control: "public, max-age=3600" - "~*\\.html$": - root: "./docs" - cache_control: "no-cache" + "~*\\.html$": + root: "./docs" + cache_control: "no-cache" - "__default__": - root: "./docs" - index_file: "index.html"
ASGI applications are mounted via the asgi extension:
extensions: - - type: asgi - config: - mounts: - - path: "/api" - app_path: "myapp.api:app" - app_type: asgi - name: "api-app" - strip_path: true+
extensions:
+ - type: asgi
+ config:
+ mounts:
+ - path: "/api"
+ app_path: "myapp.api:app"
+ app_type: asgi
+ name: "api-app"
+ strip_path: true
FastAPI applications are native ASGI:
-# myapp/api.py +-+ return [{"id": 1, "name": "Alice"}]# myapp/api.py from fastapi import FastAPI app = FastAPI() @app.get("/users") async def get_users(): - return [{"id": 1, "name": "Alice"}]
extensions: - - type: asgi - config: - mounts: - - path: "/api" - app_path: "myapp.api:app" - app_type: asgi - name: "fastapi-app"+
extensions:
+ - type: asgi
+ config:
+ mounts:
+ - path: "/api"
+ app_path: "myapp.api:app"
+ app_type: asgi
+ name: "fastapi-app"
With this configuration:
Flask applications are WSGI and will be automatically wrapped:
-# myapp/flask_api.py +-+ return {"message": "Hello from Flask!"}# myapp/flask_api.py from flask import Flask app = Flask(__name__) @app.route("/hello") def hello(): - return {"message": "Hello from Flask!"}
extensions: - - type: asgi - config: - mounts: - - path: "/flask" - app_path: "myapp.flask_api:app" - app_type: wsgi - name: "flask-app"+
extensions:
+ - type: asgi
+ config:
+ mounts:
+ - path: "/flask"
+ app_path: "myapp.flask_api:app"
+ app_type: wsgi
+ name: "flask-app"
a2wsgi or asgiref
@@ -138,19 +138,19 @@ def hello():
Django can be mounted using its ASGI application:
-extensions: - - type: asgi - config: - mounts: - - path: "/django" - django_settings: "myproject.settings" - module_path: "/path/to/django/project" - name: "django-app"+
extensions:
+ - type: asgi
+ config:
+ mounts:
+ - path: "/django"
+ django_settings: "myproject.settings"
+ module_path: "/path/to/django/project"
+ name: "django-app"
Use factory functions to create apps with custom configuration:
-# myapp/api.py +-+ return app# myapp/api.py from fastapi import FastAPI def create_app(debug: bool = False, prefix: str = "/v1") -> FastAPI: @@ -160,19 +160,19 @@ def create_app(debug: bool = False, prefix: str = "/v1") -> FastAPI: async def status(): return {"debug": debug} - return app
extensions: - - type: asgi - config: - mounts: - - path: "/api" - app_path: "myapp.api:create_app" - app_type: asgi - factory: true - factory_args: - debug: true - prefix: "/v2"+
extensions:
+ - type: asgi
+ config:
+ mounts:
+ - path: "/api"
+ app_path: "myapp.api:create_app"
+ app_type: asgi
+ factory: true
+ factory_args:
+ debug: true
+ prefix: "/v2"
By default, strip_path: true removes the mount prefix from requests:
Mount multiple applications at different paths:
-extensions: - - type: asgi - config: - mounts: - # FastAPI for REST API - - path: "/api" - app_path: "apps.api:app" - app_type: asgi ++ # Standard routing for static files + - type: routing + config: + regex_locations: + "__default__": + root: "./static"extensions: + - type: asgi + config: + mounts: + # FastAPI for REST API + - path: "/api" + app_path: "apps.api:app" + app_type: asgi - # Flask admin panel - - path: "/admin" - app_path: "apps.admin:app" - app_type: wsgi + # Flask admin panel + - path: "/admin" + app_path: "apps.admin:app" + app_type: wsgi - # Starlette websocket handler - - path: "/ws" - app_path: "apps.websocket:app" - app_type: asgi + # Starlette websocket handler + - path: "/ws" + app_path: "apps.websocket:app" + app_type: asgi - # Standard routing for static files - - type: routing - config: - regex_locations: - "__default__": - root: "./static"
Mounts are matched by path length (longest first). Given mounts at @@ -236,24 +236,24 @@ def create_app(debug: bool = False, prefix: str = "/v1") -> FastAPI:
ASGI mounts work alongside the routing extension. The asgi extension
should be listed before routing to handle mounted paths first:
extensions: - # ASGI apps handle /api/* and /admin/* - - type: asgi - config: - mounts: - - path: "/api" - app_path: "myapp:api" - app_type: asgi ++ # Routing handles everything else + - type: routing + config: + regex_locations: + "=/health": + return: "200 OK" + "__default__": + spa_fallback: true + root: "./dist"extensions: + # ASGI apps handle /api/* and /admin/* + - type: asgi + config: + mounts: + - path: "/api" + app_path: "myapp:api" + app_type: asgi - # Routing handles everything else - - type: routing - config: - regex_locations: - "=/health": - return: "200 OK" - "__default__": - spa_fallback: true - root: "./dist"
For programmatic mounting, see ASGI Mount API Reference.
diff --git a/guides/configuration.html b/guides/configuration.html index c57bcc6..8e1b54b 100644 --- a/guides/configuration.html +++ b/guides/configuration.html @@ -131,42 +131,42 @@List of extension modules to load. See Extensions Reference.
http: - static_dir: ./static - templates_dir: ./templates ++extensions: + - type: routing + config: + regex_locations: + "__default__": + root: "./static" + index_file: "index.html"http: + static_dir: ./static + templates_dir: ./templates -server: - host: 0.0.0.0 - port: 8080 - backlog: 5 - default_root: false - proxy_timeout: 30.0 +server: + host: 0.0.0.0 + port: 8080 + backlog: 5 + default_root: false + proxy_timeout: 30.0 -ssl: - enabled: false - cert_file: ./ssl/cert.pem - key_file: ./ssl/key.pem +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 - timestamp_format: "%Y-%m-%d %H:%M:%S" - files: - - path: ./logs/pyserve.log - level: DEBUG - max_bytes: 10485760 - backup_count: 5 +logging: + level: INFO + console_output: true + format: + type: standard + use_colors: true + timestamp_format: "%Y-%m-%d %H:%M:%S" + files: + - path: ./logs/pyserve.log + level: DEBUG + max_bytes: 10485760 + backup_count: 5 -extensions: - - type: routing - config: - regex_locations: - "__default__": - root: "./static" - index_file: "index.html"
++PyServe Gateway (:8000) │ ┌────────────────┼────────────────┐ @@ -49,25 +49,25 @@ FastAPI Flask Starlette :9001 :9002 :9003 /api/* /admin/* /ws/* -
PyServe acts as a gateway, routing requests to the appropriate subprocess based on URL path.
server: - host: 0.0.0.0 - port: 8000 ++ - name: admin + path: /admin + app_path: myapp.admin:appserver: + host: 0.0.0.0 + port: 8000 -extensions: - - type: process_orchestration - config: - apps: - - name: api - path: /api - app_path: myapp.api:app +extensions: + - type: process_orchestration + config: + apps: + - name: api + path: /api + app_path: myapp.api:app - - name: admin - path: /admin - app_path: myapp.admin:app
extensions: - - type: process_orchestration - config: - port_range: [9000, 9999] - health_check_enabled: true - proxy_timeout: 60.0 - logging: - httpx_level: warning - proxy_logs: true - health_check_logs: false - apps: - # ...+
extensions:
+ - type: process_orchestration
+ config:
+ port_range: [9000, 9999]
+ health_check_enabled: true
+ proxy_timeout: 60.0
+ logging:
+ httpx_level: warning
+ proxy_logs: true
+ health_check_logs: false
+ apps:
+ # ...
# myapp/api.py +-+ return [{"id": 1, "name": "Alice"}]# myapp/api.py from fastapi import FastAPI app = FastAPI() @@ -172,22 +172,22 @@ async def health(): @app.get("/users") async def get_users(): - return [{"id": 1, "name": "Alice"}]
extensions: - - type: process_orchestration - config: - apps: - - name: api - path: /api - app_path: myapp.api:app - workers: 4 - health_check_path: /health+
extensions:
+ - type: process_orchestration
+ config:
+ apps:
+ - name: api
+ path: /api
+ app_path: myapp.api:app
+ workers: 4
+ health_check_path: /health
Requests to /api/users are proxied to the FastAPI process as /users.
# myapp/admin.py +-+ return {"page": "dashboard"}# myapp/admin.py from flask import Flask app = Flask(__name__) @@ -198,17 +198,17 @@ def health(): @app.route("/dashboard") def dashboard(): - return {"page": "dashboard"}
extensions: - - type: process_orchestration - config: - apps: - - name: admin - path: /admin - app_path: myapp.admin:app - app_type: wsgi - workers: 2+
extensions:
+ - type: process_orchestration
+ config:
+ apps:
+ - name: admin
+ path: /admin
+ app_path: myapp.admin:app
+ app_type: wsgi
+ workers: 2
a2wsgi package:
@@ -216,7 +216,7 @@ def dashboard():
# myapp/api.py +-+ return app# myapp/api.py from fastapi import FastAPI def create_app(debug: bool = False) -> FastAPI: @@ -226,49 +226,49 @@ def create_app(debug: bool = False) -> FastAPI: async def health(): return {"status": "ok", "debug": debug} - return app
apps: - - name: api - path: /api - app_path: myapp.api:create_app - factory: true+
apps:
+ - name: api
+ path: /api
+ app_path: myapp.api:create_app
+ factory: true
Pass environment variables to subprocesses:
-apps: - - name: api - path: /api - app_path: myapp.api:app - env: - DATABASE_URL: "postgresql://localhost/mydb" - REDIS_URL: "redis://localhost:6379" - DEBUG: "false"+
apps:
+ - name: api
+ path: /api
+ app_path: myapp.api:app
+ env:
+ DATABASE_URL: "postgresql://localhost/mydb"
+ REDIS_URL: "redis://localhost:6379"
+ DEBUG: "false"
extensions: - - type: process_orchestration - config: - port_range: [9000, 9999] - apps: - # FastAPI REST API - - name: api - path: /api - app_path: apps.api:app - workers: 4 ++ # Starlette WebSocket Handler + - name: websocket + path: /ws + app_path: apps.websocket:app + workers: 1extensions: + - type: process_orchestration + config: + port_range: [9000, 9999] + apps: + # FastAPI REST API + - name: api + path: /api + app_path: apps.api:app + workers: 4 - # Flask Admin Panel - - name: admin - path: /admin - app_path: apps.admin:app - app_type: wsgi - workers: 2 + # Flask Admin Panel + - name: admin + path: /admin + app_path: apps.admin:app + app_type: wsgi + workers: 2 - # Starlette WebSocket Handler - - name: websocket - path: /ws - app_path: apps.websocket:app - workers: 1
PyServe automatically generates and propagates X-Request-ID headers:
Use the proxy_pass directive in routing:
extensions: - - type: routing - config: - regex_locations: - "~^/api/": - proxy_pass: "http://localhost:9001"+
extensions:
+ - type: routing
+ config:
+ regex_locations:
+ "~^/api/":
+ proxy_pass: "http://localhost:9001"
All requests to /api/* will be forwarded to http://localhost:9001/api/*.
Add custom headers to proxied requests:
-"~^/api/": - proxy_pass: "http://localhost:9001" - headers: - - "X-Custom-Header: my-value" - - "Authorization: Bearer token123"+
"~^/api/":
+ proxy_pass: "http://localhost:9001"
+ headers:
+ - "X-Custom-Header: my-value"
+ - "Authorization: Bearer token123"
Use regex capture groups to build dynamic headers:
-"~^/api/v(?P<version>\\d+)/(?P<service>\\w+)": - proxy_pass: "http://localhost:9001" - headers: - - "X-API-Version: {version}" - - "X-Service: {service}" - - "X-Client-IP: $remote_addr"+
"~^/api/v(?P<version>\\d+)/(?P<service>\\w+)":
+ proxy_pass: "http://localhost:9001"
+ headers:
+ - "X-API-Version: {version}"
+ - "X-Service: {service}"
+ - "X-Client-IP: $remote_addr"
Special variables:
Configure timeout for proxy requests:
-# Global default timeout -server: - proxy_timeout: 30.0 ++# Per-route timeout +extensions: + - type: routing + config: + regex_locations: + "~^/api/slow": + proxy_pass: "http://localhost:9001" + timeout: 120 # 2 minutes for slow endpoints# Global default timeout +server: + proxy_timeout: 30.0 -# Per-route timeout -extensions: - - type: routing - config: - regex_locations: - "~^/api/slow": - proxy_pass: "http://localhost:9001" - timeout: 120 # 2 minutes for slow endpoints
The proxy preserves the original request path by default:
-# Request: GET /api/users/123 -# Proxied: GET http://backend:9001/api/users/123 -"~^/api/": - proxy_pass: "http://backend:9001"+
# Request: GET /api/users/123
+# Proxied: GET http://backend:9001/api/users/123
+"~^/api/":
+ proxy_pass: "http://backend:9001"
To proxy to a specific path:
-# Request: GET /api/users/123 -# Proxied: GET http://backend:9001/v2/users/123 (path preserved) -"~^/api/": - proxy_pass: "http://backend:9001/v2"+
# Request: GET /api/users/123
+# Proxied: GET http://backend:9001/v2/users/123 (path preserved)
+"~^/api/":
+ proxy_pass: "http://backend:9001/v2"
Route different services to different backends:
-extensions: - - type: routing - config: - regex_locations: - "~^/api/users": - proxy_pass: "http://user-service:8001" ++ "~^/api/products": + proxy_pass: "http://product-service:8003"extensions: + - type: routing + config: + regex_locations: + "~^/api/users": + proxy_pass: "http://user-service:8001" - "~^/api/orders": - proxy_pass: "http://order-service:8002" + "~^/api/orders": + proxy_pass: "http://order-service:8002" - "~^/api/products": - proxy_pass: "http://product-service:8003"
pyserve returns appropriate error codes for proxy failures:
diff --git a/guides/routing.html b/guides/routing.html index 233059e..8294381 100644 --- a/guides/routing.html +++ b/guides/routing.html @@ -62,29 +62,29 @@Routing is configured via the routing extension:
extensions: - - type: routing - config: - regex_locations: - # Exact match for health check - "=/health": - return: "200 OK" - content_type: "text/plain" ++ # Default fallback + "__default__": + root: "./static" + index_file: "index.html"extensions: + - type: routing + config: + regex_locations: + # Exact match for health check + "=/health": + return: "200 OK" + content_type: "text/plain" - # Static files with caching - "~*\\.(js|css|png|jpg|gif|ico)$": - root: "./static" - cache_control: "public, max-age=31536000" + # Static files with caching + "~*\\.(js|css|png|jpg|gif|ico)$": + root: "./static" + cache_control: "public, max-age=31536000" - # HTML files without caching - "~*\\.html$": - root: "./static" - cache_control: "no-cache" + # HTML files without caching + "~*\\.html$": + root: "./static" + cache_control: "no-cache" - # Default fallback - "__default__": - root: "./static" - index_file: "index.html"
Regex locations support named capture groups that can be used in headers and proxy URLs:
-"~^/api/v(?P<version>\\d+)/(?P<resource>\\w+)": - proxy_pass: "http://backend:9001" - headers: - - "X-API-Version: {version}" - - "X-Resource: {resource}"+
"~^/api/v(?P<version>\\d+)/(?P<resource>\\w+)":
+ proxy_pass: "http://backend:9001"
+ headers:
+ - "X-API-Version: {version}"
+ - "X-Resource: {resource}"
Request to /api/v2/users will have headers:
For Single Page Applications, use spa_fallback with exclude_patterns:
"__default__": - spa_fallback: true - root: "./dist" - index_file: "index.html" - exclude_patterns: - - "/api/" - - "/assets/" - - "/static/"+
"__default__":
+ spa_fallback: true
+ root: "./dist"
+ index_file: "index.html"
+ exclude_patterns:
+ - "/api/"
+ - "/assets/"
+ - "/static/"
This will:
Basic static file configuration:
-"~*\\.(css|js|png|jpg|gif|svg|woff2?)$": - root: "./static" - cache_control: "public, max-age=86400" - headers: - - "X-Content-Type-Options: nosniff"+
"~*\\.(css|js|png|jpg|gif|svg|woff2?)$":
+ root: "./static"
+ cache_control: "public, max-age=86400"
+ headers:
+ - "X-Content-Type-Options: nosniff"
Loads and manages ASGI/WSGI applications from Python import paths.
-from pyserve import ASGIAppLoader ++ app_path="mymodule:app", + app_type="asgi", + module_path="/path/to/project", + factory=False, + factory_args=None +)from pyserve import ASGIAppLoader loader = ASGIAppLoader() -# Load an ASGI app +# Load an ASGI app app = loader.load_app( - app_path="mymodule:app", - app_type="asgi", - module_path="/path/to/project", - factory=False, - factory_args=None -)
Represents an application mounted at a specific path.
-from pyserve import MountedApp ++ name="my-api", + strip_path=True +)from pyserve import MountedApp mount = MountedApp( - path="/api", + path="/api", app=my_asgi_app, - name="my-api", - strip_path=True -)
Manages multiple mounted applications and routes requests.
-from pyserve import ASGIMountManager ++ path="/flask", + app_path="myapp:flask_app", + app_type="wsgi" +)from pyserve import ASGIMountManager manager = ASGIMountManager() -# Mount using app instance -manager.mount(path="/api", app=my_app) +# Mount using app instance +manager.mount(path="/api", app=my_app) -# Mount using import path +# Mount using import path manager.mount( - path="/flask", - app_path="myapp:flask_app", - app_type="wsgi" -)
Convenience functions for loading specific framework applications:
from pyserve import create_fastapi_app ++ app_path="myapp.api:app", + module_path=None, + factory=False, + factory_args=None +)from pyserve import create_fastapi_app app = create_fastapi_app( - app_path="myapp.api:app", - module_path=None, - factory=False, - factory_args=None -)
from pyserve import create_flask_app ++ app_path="myapp.web:app", + module_path=None, + factory=False, + factory_args=None +)from pyserve import create_flask_app app = create_flask_app( - app_path="myapp.web:app", - module_path=None, - factory=False, - factory_args=None -)
Automatically wraps the WSGI app for ASGI compatibility.
from pyserve import create_django_app ++ settings_module="myproject.settings", + module_path="/path/to/project" +)from pyserve import create_django_app app = create_django_app( - settings_module="myproject.settings", - module_path="/path/to/project" -)
Sets DJANGO_SETTINGS_MODULE and returns Django's ASGI application.
from pyserve import create_starlette_app ++ app_path="myapp:starlette_app", + module_path=None, + factory=False, + factory_args=None +)from pyserve import create_starlette_app app = create_starlette_app( - app_path="myapp:starlette_app", - module_path=None, - factory=False, - factory_args=None -)
Complete example mounting multiple applications:
-from pyserve import ( ++# List mounts +for mount in mounts.list_mounts(): + print(f"Mounted {mount['name']} at {mount['path']}")from pyserve import ( PyServeServer, ASGIMountManager, create_fastapi_app, create_flask_app ) -# Create mount manager +# Create mount manager mounts = ASGIMountManager() -# Mount FastAPI -api_app = create_fastapi_app("myapp.api:app") -if api_app: - mounts.mount("/api", app=api_app, name="api") +# Mount FastAPI +api_app = create_fastapi_app("myapp.api:app") +if api_app: + mounts.mount("/api", app=api_app, name="api") -# Mount Flask -admin_app = create_flask_app("myapp.admin:app") -if admin_app: - mounts.mount("/admin", app=admin_app, name="admin") +# Mount Flask +admin_app = create_flask_app("myapp.admin:app") +if admin_app: + mounts.mount("/admin", app=admin_app, name="admin") -# List mounts -for mount in mounts.list_mounts(): - print(f"Mounted {mount['name']} at {mount['path']}")
All loader functions return None on failure and log errors.
Check the return value before using:
app = create_fastapi_app("nonexistent:app") -if app is None: - # Handle error - check logs for details - print("Failed to load application")+
app = create_fastapi_app("nonexistent:app")
+if app is None:
+ # Handle error - check logs for details
+ print("Failed to load application")
For WSGI applications, pyserve uses adapters in this priority:
@@ -233,9 +233,9 @@ admin_app = create_flask_app("myapp.admin:app")Install an adapter:
-pip install a2wsgi # recommended -# or -pip install asgiref+
pip install a2wsgi # recommended
+# or
+pip install asgiref
pyserve provides a command-line interface for server management.
pyserve [OPTIONS]+
pyserve [OPTIONS]
Start with default configuration:
-pyserve+
pyserve
Start with custom config file:
-pyserve -c /path/to/config.yaml+
pyserve -c /path/to/config.yaml
Override host and port:
-pyserve --host 127.0.0.1 --port 9000+
pyserve --host 127.0.0.1 --port 9000
Enable debug mode:
-pyserve --debug+
pyserve --debug
Show version:
-pyserve --version
-# Output: pyserve 0.7.0
+ pyserve --version
+# Output: pyserve 0.7.0
Settings are applied in the following order (later overrides earlier):
@@ -125,15 +125,15 @@When working with the source repository, use make commands:
-make run # Start in development mode -make run-prod # Start in production mode -make test # Run tests -make test-cov # Tests with coverage -make lint # Check code with linters -make format # Format code -make build # Build wheel package -make clean # Clean temporary files -make help # Show all commands+
make run # Start in development mode
+make run-prod # Start in production mode
+make test # Run tests
+make test-cov # Tests with coverage
+make lint # Check code with linters
+make format # Format code
+make build # Build wheel package
+make clean # Clean temporary files
+make help # Show all commands