```markdown ┌─────────────────────────────────────────────────┐ │ │ │ ██████╗ █████╗ ███████╗████████╗███████╗ │ │ ██╔══██╗██╔══██╗██╔════╝╚══██╔══╝██╔════╝ │ │ ██████╔╝███████║███████╗ ██║ █████╗ │ │ ██╔═══╝ ██╔══██║╚════██║ ██║ ██╔══╝ │ │ ██║ ██║ ██║███████║ ██║ ███████╗ │ │ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚══════╝ │ │ │ │ Python Api-first Scalable Task Engine │ │ │ └─────────────────────────────────────────────────┘ ``` # PASTE — A Production-Ready Lightweight Python Framework > A minimalist, battle-tested Python framework for building api services with **built-in RBAC, JWT, async tasks, Snowflake IDs, Swagger, and modular utilities** — zero boilerplate, maximum control. Built on top of **Tornado**. ![License](https://img.shields.io/badge/license-MIT-blue) ![Python](https://img.shields.io/badge/python-3.11%2B-blue) ![Build](https://img.shields.io/badge/build-passing-brightgreen) --- ## ✨ Core Features | Feature | Description | |---------|-------------| | 🚀 **Auto-Loading Handlers** | Define `route_pattern = "/user"` in your handler → API is automatically registered. No router config needed. | | 🔐 **RBAC with Dynamic Rules** | Permissions stored as serialized Python objects in DB. Define complex rules as classes (e.g., `TimeBasedRule`, `IPWhitelistRule`). | | 📜 **Swagger UI Auto-Generated** | Built-in Swagger UI at `/docs`. No YAML. Schema is inferred from handlers and decorators. | | 🔢 **Snowflake ID Generator** | Embedded, thread-safe, no external dependency. Generates 10K+ unique IDs/sec. | | ⚡ **Async Task Pool with Backpressure** | `run_background_task(coro)` safely manages concurrent background jobs with configurable limits. | | 🔐 **JWT + Password Hashing** | Stateless authentication via `paste.security.token`. Passwords hashed with PBKDF2-sha256. | | 🧰 **Modular Utility Library (`util/`)** | Clean, testable utilities: `ustr`, `udict`, `ufile`, `pagination`, `snow_id`, `encoder` — no pollution in `__init__.py`. | | 📦 **Layered Architecture** | `core/` (config, logging, async), `db/` (engine, models), `rbac/` (roles, rules), `web/` (handlers, app), `service/` (business logic) — clean separation. | | 🛡️ **Safe File Handling** | `sanitize_filename()` ensures cross-platform compatibility (Windows/Linux/macOS). | | 📊 **JSON Encoder for Complex Types** | Auto-serializes `datetime`, `Decimal`, `BaseModel`, `Enum`, `numpy` — no extra config. | | 📬 **Redis Stream Actor** | Reliable consumer-group-based message processing with auto zombie-task recovery. | | 🧩 **ParamAware UI Modules** | Async-preload data for Tornado UIModules — solves the age-old sync-render bottleneck. | --- ## 🛠️ Quick Start ### 1. Install ```bash git clone https://github.com/your-repo/paste.git cd paste pip install -e . ``` ### 2. Run the example All ready-to-run examples are located in the `examples/` directory: ```bash cd examples/01_hello_world python main.py ``` 👉 Open: [http://localhost:9090/hello](http://localhost:9090/hello) --- ## 📚 Examples Jump right in with these working examples: | Example | Description | Key Features Demonstrated | |---------|-------------|--------------------------| | [`01_hello_world`](examples/01_hello_world/) | Minimal paste app | Auto-loading handlers, config, response helpers | | [`02_background_task`](examples/02_background_task/) | Async background tasks | Task pool, logging, daemonized services | | [`03_redis_stream`](examples/03_redis_stream/) | Redis Stream publisher & consumer | `StreamActor`, consumer groups, message ACK | | [`04_tasks_service`](examples/04_tasks_service/) | Scheduled task service | `TaskService`, cron-like scheduling, PID management | | [`05_gen_models`](examples/05_gen_models/) | Auto-generate DB models from existing tables | SQLAlchemy model generation | Each example includes a `config.json`, a `handler.py`, and a `main.py` — ready to run. --- ## 🏗️ Framework Architecture ``` paste/ # Framework core (do NOT modify) ├── core/ # Foundation layer │ ├── config.py # Dot-path config loader (get_config("db.engine")) │ ├── logging.py # Logger with rotating file + console │ └── aio_pool.py # Async task pool with backpressure ├── db/ # Database layer │ ├── engine.py # SQLAlchemy engine factory │ ├── redis.py # Redis connection + StreamActor │ ├── basemodel.py # Async ORM base model │ ├── basetable.py # Table reflection utilities │ ├── baseadapter.py # Result-set adapter │ └── gen_models.py # Auto-generate model classes ├── web/ # Web layer │ ├── application.py # Tornado Application with auto-loading │ ├── handler.py # Base RequestHandler with response helpers │ ├── decorators.py # @route, @auth_token, @auth_permission │ ├── swagger.py # Swagger UI auto-generation │ ├── form.py # WTForms integration │ ├── param_aware_loader.py # Async UIModule data preloader │ └── websocket.py # WebSocket support ├── rbac/ # Access control layer │ ├── rbac_user.py # User with permission inheritance │ ├── rbac_role.py # Role management │ ├── rbac_rule.py # Rule engine (pickle-serialized) │ ├── rbac_item.py # Permission item hierarchy │ ├── rbac_assignment.py # User-item assignment │ └── rbac_permission.py # Permission query ├── security/ # Security layer │ ├── token.py # JWT encode/decode with configurable issuer │ └── shash.py # PBKDF2-sha256 password hashing ├── service/ # Service layer │ ├── server.py # Server process management │ ├── daemonize.py # Unix daemon + PID file │ └── task_service.py # Scheduled task runner ├── chart/ # Chart generation (optional) │ ├── bar.py / pie.py / line.py │ └── ... └── util/ # Utility library ├── ustr.py / udict.py # String & dict helpers ├── ufile.py # File operations with sanitized filenames ├── pagination.py # Page, Pagination, CursorPagination ├── snow_id.py # Snowflake ID generator ├── encoder.py # JSON encoder + BaseX decoder ├── pdf.py / svg.py / xlsx.py └── ... ``` Your application code lives **entirely outside** `paste/`: ``` myapp/ ├── main.py # Entry point: create Application, listen, start ├── config.json # DB, logger, RBAC, Tornado configuration ├── apps/ # Your handlers (recommended) │ └── demo/ │ ├── __init__.py │ ├── handler_user.py │ └── handler_auth.py ├── models/ # Your DB models │ └── db_models.py └── service/ # Your background services └── __init__.py ``` --- ## 🔐 RBAC Example ```python # handler.py from paste.rbac.rbac_user import RbacUser from paste.web.decorators import route, auth_token, auth_permission from paste.web.handler import RequestHandler @route("/admin/users") class AdminUserHandler(RequestHandler): @auth_token @auth_permission async def get(self): users = await RbacUser.query_as_df(RbacUser().gen_query()) self.response_ok(rows=users.to_dict('records')) ``` ```python # rule.py — dynamic rule as a serialized Python class from datetime import datetime from paste.rbac.rbac_rule import RbacRule class BusinessTimeRule(RbacRule): async def run(self, **kwargs) -> bool: hour = datetime.now().hour return 9 <= hour < 18 # only allow during business hours ``` --- ## ⚙️ Configuration All configuration lives in a single `config.json`: ```json { "tornado": { "demo": { "autoreload": false, "handlers_pkg": "apps.demo", "port": 9090, "static_path": "static", "template_path": "templates", "swagger_title": "DemoAPI", "swagger_description": "Demo API", "swagger_api_version": "1.0.1" } }, "db": { "engine": { "engine": "mysql+pymysql://user:pass@localhost:3306/mydb", "async_engine": "mysql+aiomysql://user:pass@localhost:3306/mydb", "engine_option": { "echo": false, "pool_size": 10 } } }, "redis": { "connection": "redis://localhost:6379/0", "streams": { "user_event": { "group": "user_event_group", "consumer": "processor_01" } } }, "rbac": { "user_class": "myapp.models.MyRbacUser", "table": { "rule": "rbac_rule", "user": "rbac_user", "item": "rbac_item", "assignment": "rbac_assignment", "item_child": "rbac_item_child" } }, "logger": { "default": { "basic": { "filename": "logs/app.log", "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s", "level": 20 } } } } ``` Access any config value with dot-path: ```python from paste.core import config engine_url = config.get_config("db.engine.engine") port = config.get_config("tornado.demo.port", 9000) ``` --- ## 📣 License MIT © 2025 [Wayne Zhang / Organization] --- ## 🌍 Contributing Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on: - Code style (Black + flake8 + mypy) - How to submit issues and pull requests --- ## 🧪 Running Tests ```bash pytest # Run all tests pytest tests/unit # Run unit tests only (no external services) pytest tests/integration # Run integration tests (require DB/Redis) pytest --cov=paste # Run with coverage report ``` --- > **💡 Why paste?** > > Most frameworks make you write boilerplate. > `paste` makes you **stop writing boilerplate** — and **start building features**. > > - ✅ No `@app.get("/user")` decorators — just `route_pattern = "/user"` > - ✅ No YAML for Swagger — schema inferred automatically > - ✅ No Redis for IDs — Snowflake built-in > - ✅ No Celery for tasks — async pool with backpressure > - ✅ No config files in 5 places — one `config.json`, one `get_config("db.engine")` ```