gobstopper 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gobstopper-0.1.0/PKG-INFO +729 -0
- gobstopper-0.1.0/README.md +669 -0
- gobstopper-0.1.0/pyproject.toml +135 -0
- gobstopper-0.1.0/rust/gobstopper_core_rs/Cargo.lock +1635 -0
- gobstopper-0.1.0/rust/gobstopper_core_rs/Cargo.toml +32 -0
- gobstopper-0.1.0/rust/gobstopper_core_rs/src/json.rs +35 -0
- gobstopper-0.1.0/rust/gobstopper_core_rs/src/lib.rs +38 -0
- gobstopper-0.1.0/rust/gobstopper_core_rs/src/routing.rs +582 -0
- gobstopper-0.1.0/rust/gobstopper_core_rs/src/static_files.rs +485 -0
- gobstopper-0.1.0/rust/gobstopper_core_rs/src/template_engine.rs +289 -0
- gobstopper-0.1.0/rust/gobstopper_core_rs/src/template_streaming.rs +203 -0
- gobstopper-0.1.0/rust/gobstopper_core_rs/src/template_watcher.rs +342 -0
- gobstopper-0.1.0/rust/gobstopper_core_rs/uv.lock +7 -0
- gobstopper-0.1.0/src/gobstopper/__init__.py +134 -0
- gobstopper-0.1.0/src/gobstopper/cli/__init__.py +9 -0
- gobstopper-0.1.0/src/gobstopper/cli/main.py +589 -0
- gobstopper-0.1.0/src/gobstopper/cli/template_engine.py +763 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/blueprints_app.py.j2 +165 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/blueprints_auth.py.j2 +323 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/blueprints_init.py.j2 +38 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/cms_app.py.j2 +568 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/cms_env.j2 +91 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/cms_readme.md.j2 +361 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/cms_requirements.txt.j2 +59 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/content_models.py.j2 +318 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/dashboard_app.py.j2 +408 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/dashboard_env.j2 +51 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/dashboard_readme.md.j2 +286 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/data_science_app.py.j2 +284 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/data_science_env.j2 +48 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/data_science_readme.md.j2 +210 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/microservice_app.py.j2 +535 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/microservice_readme.md.j2 +545 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/microservice_requirements.txt.j2 +84 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/microservices_app.py.j2 +535 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/ml_models.py.j2 +185 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/modular_app.py.j2 +304 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/modules_init.py.j2 +27 -0
- gobstopper-0.1.0/src/gobstopper/cli/templates/single_app.py.j2 +708 -0
- gobstopper-0.1.0/src/gobstopper/config.py +513 -0
- gobstopper-0.1.0/src/gobstopper/core/__init__.py +11 -0
- gobstopper-0.1.0/src/gobstopper/core/app.py +2590 -0
- gobstopper-0.1.0/src/gobstopper/core/blueprint.py +112 -0
- gobstopper-0.1.0/src/gobstopper/extensions/__init__.py +0 -0
- gobstopper-0.1.0/src/gobstopper/extensions/charts/__init__.py +46 -0
- gobstopper-0.1.0/src/gobstopper/extensions/charts/builders.py +793 -0
- gobstopper-0.1.0/src/gobstopper/extensions/charts/extension.py +258 -0
- gobstopper-0.1.0/src/gobstopper/extensions/charts/filters.py +121 -0
- gobstopper-0.1.0/src/gobstopper/extensions/charts/streaming.py +131 -0
- gobstopper-0.1.0/src/gobstopper/extensions/charts/themes.py +269 -0
- gobstopper-0.1.0/src/gobstopper/extensions/charts/types.py +71 -0
- gobstopper-0.1.0/src/gobstopper/extensions/openapi/__init__.py +288 -0
- gobstopper-0.1.0/src/gobstopper/extensions/openapi/adapters_dataclasses.py +151 -0
- gobstopper-0.1.0/src/gobstopper/extensions/openapi/adapters_msgspec.py +139 -0
- gobstopper-0.1.0/src/gobstopper/extensions/openapi/adapters_typeddict.py +120 -0
- gobstopper-0.1.0/src/gobstopper/extensions/openapi/decorators.py +863 -0
- gobstopper-0.1.0/src/gobstopper/extensions/openapi/generator.py +497 -0
- gobstopper-0.1.0/src/gobstopper/extensions/openapi/typing_adapters.py +365 -0
- gobstopper-0.1.0/src/gobstopper/extensions/openapi/ui.py +140 -0
- gobstopper-0.1.0/src/gobstopper/http/__init__.py +44 -0
- gobstopper-0.1.0/src/gobstopper/http/errors.py +44 -0
- gobstopper-0.1.0/src/gobstopper/http/file_storage.py +367 -0
- gobstopper-0.1.0/src/gobstopper/http/helpers.py +414 -0
- gobstopper-0.1.0/src/gobstopper/http/multipart.py +134 -0
- gobstopper-0.1.0/src/gobstopper/http/negotiation.py +177 -0
- gobstopper-0.1.0/src/gobstopper/http/notifications.py +286 -0
- gobstopper-0.1.0/src/gobstopper/http/problem.py +21 -0
- gobstopper-0.1.0/src/gobstopper/http/request.py +1419 -0
- gobstopper-0.1.0/src/gobstopper/http/response.py +486 -0
- gobstopper-0.1.0/src/gobstopper/http/routing.py +156 -0
- gobstopper-0.1.0/src/gobstopper/http/sse.py +73 -0
- gobstopper-0.1.0/src/gobstopper/log.py +25 -0
- gobstopper-0.1.0/src/gobstopper/middleware/__init__.py +52 -0
- gobstopper-0.1.0/src/gobstopper/middleware/cors.py +208 -0
- gobstopper-0.1.0/src/gobstopper/middleware/limits.py +142 -0
- gobstopper-0.1.0/src/gobstopper/middleware/rust_static.py +444 -0
- gobstopper-0.1.0/src/gobstopper/middleware/security.py +625 -0
- gobstopper-0.1.0/src/gobstopper/middleware/static.py +174 -0
- gobstopper-0.1.0/src/gobstopper/sessions/memory_storage.py +44 -0
- gobstopper-0.1.0/src/gobstopper/sessions/redis_storage.py +46 -0
- gobstopper-0.1.0/src/gobstopper/sessions/sql_storage.py +71 -0
- gobstopper-0.1.0/src/gobstopper/sessions/storage.py +106 -0
- gobstopper-0.1.0/src/gobstopper/tasks/__init__.py +15 -0
- gobstopper-0.1.0/src/gobstopper/tasks/models.py +254 -0
- gobstopper-0.1.0/src/gobstopper/tasks/queue.py +870 -0
- gobstopper-0.1.0/src/gobstopper/tasks/storage.py +475 -0
- gobstopper-0.1.0/src/gobstopper/templates/__init__.py +40 -0
- gobstopper-0.1.0/src/gobstopper/templates/engine.py +100 -0
- gobstopper-0.1.0/src/gobstopper/templates/rust_engine.py +556 -0
- gobstopper-0.1.0/src/gobstopper/templates/validator.py +506 -0
- gobstopper-0.1.0/src/gobstopper/templates/wopr_error.html +214 -0
- gobstopper-0.1.0/src/gobstopper/utils/__init__.py +11 -0
- gobstopper-0.1.0/src/gobstopper/utils/idempotency.py +83 -0
- gobstopper-0.1.0/src/gobstopper/utils/rate_limiter.py +73 -0
- gobstopper-0.1.0/src/gobstopper/websocket/__init__.py +11 -0
- gobstopper-0.1.0/src/gobstopper/websocket/connection.py +470 -0
- gobstopper-0.1.0/src/gobstopper/websocket/manager.py +852 -0
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gobstopper
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Classifier: Development Status :: 3 - Alpha
|
|
5
|
+
Classifier: Environment :: Web Environment
|
|
6
|
+
Classifier: Framework :: AsyncIO
|
|
7
|
+
Classifier: Intended Audience :: Developers
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Requires-Dist: aiohttp==3.13.0
|
|
18
|
+
Requires-Dist: granian[reload]==2.5.5
|
|
19
|
+
Requires-Dist: loguru>=0.7.2
|
|
20
|
+
Requires-Dist: msgspec
|
|
21
|
+
Requires-Dist: rloop>=0.2.0
|
|
22
|
+
Requires-Dist: typing-extensions==4.15.0
|
|
23
|
+
Requires-Dist: anyio==4.11.0
|
|
24
|
+
Requires-Dist: jinja2>=3.1.0 ; extra == 'templates'
|
|
25
|
+
Requires-Dist: duckdb>=0.9.0 ; extra == 'tasks'
|
|
26
|
+
Requires-Dist: click>=8.0.0 ; extra == 'cli'
|
|
27
|
+
Requires-Dist: redis>=5.0 ; extra == 'redis'
|
|
28
|
+
Requires-Dist: asyncpg ; extra == 'postgres'
|
|
29
|
+
Requires-Dist: pyecharts>=2.0.0 ; extra == 'charts'
|
|
30
|
+
Requires-Dist: pytest>=7.0.0 ; extra == 'dev'
|
|
31
|
+
Requires-Dist: pytest-asyncio>=0.21.0 ; extra == 'dev'
|
|
32
|
+
Requires-Dist: black>=23.0.0 ; extra == 'dev'
|
|
33
|
+
Requires-Dist: ruff>=0.1.0 ; extra == 'dev'
|
|
34
|
+
Requires-Dist: mypy>=1.0.0 ; extra == 'dev'
|
|
35
|
+
Requires-Dist: httpx>=0.24.1 ; extra == 'dev'
|
|
36
|
+
Requires-Dist: duckdb>=0.9.0 ; extra == 'dev'
|
|
37
|
+
Requires-Dist: maturin>=1.0.0 ; extra == 'dev'
|
|
38
|
+
Requires-Dist: jinja2>=3.1.0 ; extra == 'all'
|
|
39
|
+
Requires-Dist: duckdb>=0.9.0 ; extra == 'all'
|
|
40
|
+
Requires-Dist: click>=8.0.0 ; extra == 'all'
|
|
41
|
+
Requires-Dist: pyecharts>=2.0.0 ; extra == 'all'
|
|
42
|
+
Provides-Extra: templates
|
|
43
|
+
Provides-Extra: tasks
|
|
44
|
+
Provides-Extra: cli
|
|
45
|
+
Provides-Extra: redis
|
|
46
|
+
Provides-Extra: postgres
|
|
47
|
+
Provides-Extra: charts
|
|
48
|
+
Provides-Extra: dev
|
|
49
|
+
Provides-Extra: all
|
|
50
|
+
Summary: A simple wrapper delivering complete web framework power - like Wonka's Gobstopper, wrapping RSGI complexity into Flask-like simplicity
|
|
51
|
+
Keywords: web,framework,async,rsgi,granian,background-tasks
|
|
52
|
+
Author: Gobstopper Framework Team
|
|
53
|
+
License: MIT
|
|
54
|
+
Requires-Python: >=3.10
|
|
55
|
+
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
|
|
56
|
+
Project-URL: Homepage, https://github.com/iristech-systems/gobstopper
|
|
57
|
+
Project-URL: Documentation, https://iristech-systems.github.io/Gobstopper-Docs/
|
|
58
|
+
Project-URL: Repository, https://github.com/iristech-systems/gobstopper
|
|
59
|
+
Project-URL: Issues, https://github.com/iristech-systems/gobstopper/issues
|
|
60
|
+
|
|
61
|
+
# Gobstopper Web Framework ๐ฌ
|
|
62
|
+
|
|
63
|
+
> *"Like Willy Wonka's Everlasting Gobstopper - a simple wrapper that delivers a complete multi-course meal"*
|
|
64
|
+
|
|
65
|
+
A **production-ready**, high-performance async web framework built specifically for Granian's RSGI interface. Gobstopper takes the raw power of RSGI and wraps it in a simple, elegant API - giving you a full-featured web framework that's as easy to use as Flask but as fast as raw ASGI/RSGI.
|
|
66
|
+
|
|
67
|
+
**The Magic**: Just like Wonka's magical candy that contains an entire meal in a single piece, Gobstopper wraps RSGI's complexity into a simple interface while delivering everything you need: routing, templates, WebSockets, background tasks, sessions, security, and more.
|
|
68
|
+
|
|
69
|
+
## ๐ฏ Why Gobstopper?
|
|
70
|
+
|
|
71
|
+
**Simple Wrapper, Complex Power:**
|
|
72
|
+
- ๐ฌ **Simple API**: Flask-like simplicity wrapping RSGI's raw performance
|
|
73
|
+
- โก๏ธ **RSGI Native**: Direct access to Granian's high-performance RSGI interface
|
|
74
|
+
- ๐ฆ **Rust-Accelerated**: Optional Rust components for routing, templates, and static files
|
|
75
|
+
- ๐ **Batteries Included**: Complete framework - background tasks, WebSockets, sessions, and security
|
|
76
|
+
- ๐จ **Familiar Design**: Ergonomic API with modern async/await patterns
|
|
77
|
+
- ๐ฆ **Layered Features**: Start simple, add complexity only when you need it
|
|
78
|
+
|
|
79
|
+
## ๐ Benchmarks
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
๐งช Testing Gobstopper Benchmark Endpoints
|
|
83
|
+
==================================================
|
|
84
|
+
โ
Info 1.04ms application/json
|
|
85
|
+
โ
JSON 0.44ms application/json
|
|
86
|
+
โ
Plaintext 0.35ms text/plain
|
|
87
|
+
โ
Single Query 1.51ms application/json
|
|
88
|
+
โ
5 Queries 1.64ms application/json
|
|
89
|
+
โ
3 Updates 2.77ms application/json
|
|
90
|
+
โ
Fortunes 2.72ms text/html; charset=utf-8
|
|
91
|
+
โ
10 Cached 11.91ms application/json
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## ๐ Features
|
|
95
|
+
|
|
96
|
+
### ๐ฆ **Rust-Powered Components**
|
|
97
|
+
- **Rust Router**: High-performance path routing with zero-copy parameter extraction
|
|
98
|
+
- **Rust Templates**: Blazing-fast Jinja2-compatible rendering with streaming support
|
|
99
|
+
- **Rust Static Files**: Ultra-fast static asset serving with intelligent caching
|
|
100
|
+
- **Hybrid Architecture**: Seamless fallback to Python components when Rust unavailable
|
|
101
|
+
|
|
102
|
+
### ๐ **Core Framework**
|
|
103
|
+
- **RSGI Interface**: Built specifically for Granian's high-performance RSGI protocol
|
|
104
|
+
- **Type-Safe Validation**: Automatic request validation with msgspec Struct type hints
|
|
105
|
+
- **High-Performance JSON**: msgspec-powered JSON parsing and serialization (up to 10x faster)
|
|
106
|
+
- **Async/Await**: Full async support throughout the framework stack
|
|
107
|
+
- **Background Tasks**: Intelligent task system with DuckDB persistence, priorities, and retries
|
|
108
|
+
- **WebSocket Support**: Real-time communication with room management and broadcasting
|
|
109
|
+
- **Template Engine**: Jinja2 integration with async support and hot-reload
|
|
110
|
+
- **Middleware System**: Static files, CORS, security, and custom middleware
|
|
111
|
+
|
|
112
|
+
### ๐ **Security & Production**
|
|
113
|
+
- **Security First**: CSRF protection, security headers, rate limiting, input validation
|
|
114
|
+
- **Production Ready**: Comprehensive error handling, logging, and monitoring
|
|
115
|
+
- **CLI Tools**: Project initialization, task workers, and management commands
|
|
116
|
+
- **Cross-Platform**: Native wheels for macOS ARM64, Linux x86_64/ARM64
|
|
117
|
+
- **Developer Experience**: Clean APIs, helpful error messages, hot reload
|
|
118
|
+
|
|
119
|
+
## ๐ฆ Installation
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Basic installation (core framework only)
|
|
123
|
+
uv add gobstopper
|
|
124
|
+
|
|
125
|
+
# With all optional features
|
|
126
|
+
uv add "gobstopper[all]"
|
|
127
|
+
|
|
128
|
+
# Or specific features
|
|
129
|
+
uv add "gobstopper[templates,tasks,cli,charts]"
|
|
130
|
+
|
|
131
|
+
# For production with session backends
|
|
132
|
+
uv add "gobstopper[redis,postgres]"
|
|
133
|
+
|
|
134
|
+
# Development installation
|
|
135
|
+
uv add "gobstopper[dev]"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Optional Dependencies
|
|
139
|
+
|
|
140
|
+
Gobstopper uses optional dependencies to keep the core lightweight:
|
|
141
|
+
|
|
142
|
+
- **`templates`**: Jinja2 template engine (`jinja2>=3.1.0`)
|
|
143
|
+
- **`tasks`**: Background task system with DuckDB persistence (`duckdb>=0.9.0`)
|
|
144
|
+
- **`cli`**: Command-line tools for project generation (`click>=8.0.0`)
|
|
145
|
+
- **`charts`**: Data visualization support (`pyecharts>=2.0.0`)
|
|
146
|
+
- **`redis`**: Redis session storage backend (`redis>=5.0`)
|
|
147
|
+
- **`postgres`**: PostgreSQL session storage backend (`asyncpg`)
|
|
148
|
+
- **`dev`**: Development tools (pytest, black, ruff, mypy, httpx)
|
|
149
|
+
- **`all`**: All optional features except dev dependencies
|
|
150
|
+
|
|
151
|
+
**Note**: All optional features have graceful fallbacks - the framework will work without them, but specific features will be unavailable.
|
|
152
|
+
|
|
153
|
+
## ๐ Quick Start
|
|
154
|
+
|
|
155
|
+
### Create a New Project
|
|
156
|
+
```bash
|
|
157
|
+
# Install Gobstopper with CLI tools
|
|
158
|
+
uv add "wopr[cli]"
|
|
159
|
+
|
|
160
|
+
# Create new project
|
|
161
|
+
uv run wopr init my_app
|
|
162
|
+
|
|
163
|
+
# Navigate and run
|
|
164
|
+
cd my_app
|
|
165
|
+
uv sync
|
|
166
|
+
uv run wopr run --reload
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Simple Example
|
|
170
|
+
```python
|
|
171
|
+
from gobstopper import Gobstopper, Request, jsonify
|
|
172
|
+
|
|
173
|
+
app = Gobstopper(__name__)
|
|
174
|
+
|
|
175
|
+
@app.get("/")
|
|
176
|
+
async def hello(request: Request):
|
|
177
|
+
return jsonify({"message": "Hello from Gobstopper!"})
|
|
178
|
+
|
|
179
|
+
@app.get("/users/<user_id>")
|
|
180
|
+
async def get_user(request: Request, user_id: str):
|
|
181
|
+
return jsonify({"user_id": user_id, "name": f"User {user_id}"})
|
|
182
|
+
|
|
183
|
+
# Run with: wopr run
|
|
184
|
+
# Or: wopr run --reload (with auto-reload)
|
|
185
|
+
# Or: wopr run -w 4 (with 4 workers)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## ๐ Examples
|
|
189
|
+
|
|
190
|
+
### ๐ Interactive Demo (`example_app.py`)
|
|
191
|
+
Complete showcase of all framework features with a web UI:
|
|
192
|
+
```bash
|
|
193
|
+
uv sync --extra all
|
|
194
|
+
granian --interface rsgi --reload example_app:app
|
|
195
|
+
```
|
|
196
|
+
Visit http://localhost:8000 for interactive demos of:
|
|
197
|
+
- HTTP endpoints and routing
|
|
198
|
+
- Background task processing
|
|
199
|
+
- WebSocket communication
|
|
200
|
+
- Security features
|
|
201
|
+
- Middleware functionality
|
|
202
|
+
|
|
203
|
+
### ๐งฉ Blueprints Demo (`blueprints_demo`)
|
|
204
|
+
A blueprint-structured sample app demonstrating nested blueprints, per-blueprint static/templates, WebSockets, background tasks, middleware, and rate limiting.
|
|
205
|
+
|
|
206
|
+
Run:
|
|
207
|
+
```bash
|
|
208
|
+
uv sync --extra all
|
|
209
|
+
granian --interface rsgi --reload blueprints_demo.app:app
|
|
210
|
+
# or:
|
|
211
|
+
uv run granian -w 1 -h 0.0.0.0 -p 8080 -r blueprints_demo.app:app
|
|
212
|
+
```
|
|
213
|
+
Then visit http://localhost:8080/
|
|
214
|
+
|
|
215
|
+
### ๐ Data Handling (`data_example.py`)
|
|
216
|
+
RESTful API demonstrating data operations:
|
|
217
|
+
```bash
|
|
218
|
+
granian --interface rsgi --reload data_example:app
|
|
219
|
+
```
|
|
220
|
+
Features:
|
|
221
|
+
- CRUD operations with filtering and pagination
|
|
222
|
+
- Background data processing
|
|
223
|
+
- Real-time analytics
|
|
224
|
+
- Task monitoring
|
|
225
|
+
|
|
226
|
+
### ๐ Benchmarks (`benchmark_simple.py`)
|
|
227
|
+
Standard TechEmpower benchmark implementation:
|
|
228
|
+
```bash
|
|
229
|
+
granian --interface rsgi --workers 4 --threads 2 benchmark_simple:app
|
|
230
|
+
```
|
|
231
|
+
Benchmark endpoints:
|
|
232
|
+
- JSON serialization
|
|
233
|
+
- Database queries (simulated)
|
|
234
|
+
- Database updates (simulated)
|
|
235
|
+
- Plaintext response
|
|
236
|
+
- HTML template rendering
|
|
237
|
+
- Cached queries
|
|
238
|
+
|
|
239
|
+
## ๐๏ธ Architecture
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
src/wopr/
|
|
243
|
+
โโโ core/ # Main Gobstopper application class
|
|
244
|
+
โโโ http/ # Request/Response handling & routing
|
|
245
|
+
โโโ websocket/ # WebSocket support & room management
|
|
246
|
+
โโโ tasks/ # Background task system with DuckDB
|
|
247
|
+
โโโ templates/ # Jinja2 template engine
|
|
248
|
+
โโโ middleware/ # Static files, CORS, security
|
|
249
|
+
โโโ cli/ # Command-line tools
|
|
250
|
+
โโโ utils/ # Rate limiting and utilities
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## ๐ง Key Components
|
|
254
|
+
|
|
255
|
+
### Application & Type-Safe Validation
|
|
256
|
+
```python
|
|
257
|
+
from gobstopper import Gobstopper
|
|
258
|
+
from msgspec import Struct
|
|
259
|
+
|
|
260
|
+
app = Gobstopper(__name__, debug=True)
|
|
261
|
+
app.init_templates() # Enable Jinja2 templates
|
|
262
|
+
|
|
263
|
+
# Define data models with automatic validation
|
|
264
|
+
class User(Struct):
|
|
265
|
+
name: str
|
|
266
|
+
email: str
|
|
267
|
+
age: int = 0 # Optional field with default
|
|
268
|
+
|
|
269
|
+
class UpdateUser(Struct):
|
|
270
|
+
name: str = None # All fields optional for updates
|
|
271
|
+
email: str = None
|
|
272
|
+
|
|
273
|
+
# Routes with automatic validation
|
|
274
|
+
@app.post("/api/users")
|
|
275
|
+
async def create_user(request, user: User):
|
|
276
|
+
# user is automatically validated and typed!
|
|
277
|
+
# No manual request.json() or validation needed
|
|
278
|
+
return {"message": f"Created user: {user.name}"}
|
|
279
|
+
|
|
280
|
+
@app.put("/api/users/<user_id>")
|
|
281
|
+
async def update_user(request, user_id: str, updates: UpdateUser):
|
|
282
|
+
# Path params + validated body automatically injected
|
|
283
|
+
return {"updated": user_id, "changes": updates}
|
|
284
|
+
|
|
285
|
+
# Manual JSON parsing still available
|
|
286
|
+
@app.post("/api/data")
|
|
287
|
+
async def manual_data(request):
|
|
288
|
+
data = await request.get_json() # msgspec powered
|
|
289
|
+
return {"received": data}
|
|
290
|
+
|
|
291
|
+
# Middleware
|
|
292
|
+
from gobstopper.middleware import CORSMiddleware
|
|
293
|
+
app.add_middleware(CORSMiddleware(origins=["*"]))
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Background Tasks
|
|
297
|
+
```python
|
|
298
|
+
@app.task("send_email", "notifications")
|
|
299
|
+
async def send_email(to: str, subject: str):
|
|
300
|
+
# Task implementation
|
|
301
|
+
return {"status": "sent"}
|
|
302
|
+
|
|
303
|
+
# Queue tasks
|
|
304
|
+
task_id = await app.add_background_task(
|
|
305
|
+
"send_email", "notifications", TaskPriority.HIGH,
|
|
306
|
+
to="user@example.com", subject="Welcome!"
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
# Start workers
|
|
310
|
+
await app.start_task_workers("notifications", worker_count=2)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### WebSocket
|
|
314
|
+
```python
|
|
315
|
+
@app.websocket("/ws/chat")
|
|
316
|
+
async def chat_handler(websocket):
|
|
317
|
+
await websocket.accept()
|
|
318
|
+
|
|
319
|
+
while True:
|
|
320
|
+
message = await websocket.receive()
|
|
321
|
+
await websocket.send_text(f"Echo: {message.data}")
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Templates
|
|
325
|
+
```python
|
|
326
|
+
@app.get("/")
|
|
327
|
+
async def index(request):
|
|
328
|
+
return await app.render_template("index.html",
|
|
329
|
+
message="Hello World!")
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### File Uploads
|
|
333
|
+
```python
|
|
334
|
+
from gobstopper import FileStorage, secure_filename, send_from_directory
|
|
335
|
+
|
|
336
|
+
@app.post("/upload")
|
|
337
|
+
async def upload_file(request):
|
|
338
|
+
files = await request.get_files()
|
|
339
|
+
|
|
340
|
+
if 'document' in files:
|
|
341
|
+
file: FileStorage = files['document']
|
|
342
|
+
filename = secure_filename(file.filename)
|
|
343
|
+
file.save(f"uploads/{filename}")
|
|
344
|
+
return {"uploaded": filename}
|
|
345
|
+
|
|
346
|
+
return {"error": "No file"}, 400
|
|
347
|
+
|
|
348
|
+
@app.get("/files/<path:filename>")
|
|
349
|
+
async def serve_file(request, filename: str):
|
|
350
|
+
return send_from_directory("uploads", filename)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Flask/Quart Convenience Features
|
|
354
|
+
```python
|
|
355
|
+
from gobstopper import abort, make_response, notification
|
|
356
|
+
|
|
357
|
+
@app.get("/users/<user_id>")
|
|
358
|
+
async def get_user(request, user_id: str):
|
|
359
|
+
if not user_id.isdigit():
|
|
360
|
+
abort(400, "Invalid user ID")
|
|
361
|
+
|
|
362
|
+
user = find_user(user_id)
|
|
363
|
+
if not user:
|
|
364
|
+
abort(404, "User not found")
|
|
365
|
+
|
|
366
|
+
return {"user": user}
|
|
367
|
+
|
|
368
|
+
@app.post("/users")
|
|
369
|
+
async def create_user(request):
|
|
370
|
+
# Flash-style notifications
|
|
371
|
+
notification(request, "User created successfully!", "success")
|
|
372
|
+
|
|
373
|
+
# Flexible response building
|
|
374
|
+
response = make_response({"id": 123}, 201, {"X-User-ID": "123"})
|
|
375
|
+
return response
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
## ๐ ๏ธ CLI Tools
|
|
379
|
+
|
|
380
|
+
Gobstopper includes a comprehensive CLI for rapid development and project management:
|
|
381
|
+
|
|
382
|
+
### ๐ Running Your Application
|
|
383
|
+
|
|
384
|
+
```bash
|
|
385
|
+
# Basic usage (Flask-like interface)
|
|
386
|
+
wopr run
|
|
387
|
+
|
|
388
|
+
# With auto-reload for development
|
|
389
|
+
wopr run --reload
|
|
390
|
+
|
|
391
|
+
# Production with multiple workers
|
|
392
|
+
wopr run -w 4
|
|
393
|
+
|
|
394
|
+
# Custom host and port
|
|
395
|
+
wopr run -h 0.0.0.0 -p 3000
|
|
396
|
+
|
|
397
|
+
# Specific app module
|
|
398
|
+
wopr run myapp:app
|
|
399
|
+
|
|
400
|
+
# Load from configuration file
|
|
401
|
+
wopr run --config dev # Loads dev.json or dev.toml
|
|
402
|
+
wopr run --config production # Loads production.json or production.toml
|
|
403
|
+
|
|
404
|
+
# Override config with CLI arguments
|
|
405
|
+
wopr run --config production -w 8 # Use production config but override workers
|
|
406
|
+
|
|
407
|
+
# All options
|
|
408
|
+
wopr run -w 4 -t 2 -h 0.0.0.0 -p 8080 --reload
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
**Configuration Files:**
|
|
412
|
+
|
|
413
|
+
Create `dev.json`, `production.json`, or use TOML format:
|
|
414
|
+
|
|
415
|
+
```json
|
|
416
|
+
{
|
|
417
|
+
"app": "myapp:app",
|
|
418
|
+
"host": "0.0.0.0",
|
|
419
|
+
"port": 8080,
|
|
420
|
+
"workers": 4,
|
|
421
|
+
"threads": 2,
|
|
422
|
+
"reload": false
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
```toml
|
|
427
|
+
# production.toml
|
|
428
|
+
app = "myapp:app"
|
|
429
|
+
host = "0.0.0.0"
|
|
430
|
+
port = 8080
|
|
431
|
+
workers = 4
|
|
432
|
+
threads = 2
|
|
433
|
+
reload = false
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**Platform-Optimized Performance:**
|
|
437
|
+
- ๐ **ARM (Apple Silicon)**: Automatically uses `--runtime-mode st` (single-threaded)
|
|
438
|
+
- ๐ป **x86_64 (Intel/AMD)**: Automatically uses `--runtime-mode mt` (multi-threaded)
|
|
439
|
+
|
|
440
|
+
**Built-in Granian Optimizations:**
|
|
441
|
+
- `--log-level error`: Minimal logging overhead
|
|
442
|
+
- `--backlog 16384`: Large connection backlog for high throughput
|
|
443
|
+
- `--loop rloop`: High-performance event loop
|
|
444
|
+
- `--respawn-failed-workers`: Automatic worker recovery
|
|
445
|
+
|
|
446
|
+
### ๐ Project Generation
|
|
447
|
+
|
|
448
|
+
```bash
|
|
449
|
+
# Interactive project setup
|
|
450
|
+
wopr init
|
|
451
|
+
|
|
452
|
+
# Create specific project types
|
|
453
|
+
wopr init my-api --usecase data-science --structure modular
|
|
454
|
+
wopr init my-cms --usecase content-management --structure blueprints
|
|
455
|
+
wopr init dashboard --usecase real-time-dashboard --structure microservices
|
|
456
|
+
wopr init simple-app --usecase microservice --structure single
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
**Available Use Cases:**
|
|
460
|
+
- **`data-science`**: ML APIs with data processing, model endpoints, and analytics
|
|
461
|
+
- **`real-time-dashboard`**: Live dashboards with WebSocket streaming and data visualization
|
|
462
|
+
- **`content-management`**: Full CMS with admin interface, user management, and content APIs
|
|
463
|
+
- **`microservice`**: Lightweight service architecture for distributed systems
|
|
464
|
+
|
|
465
|
+
**Available Structures:**
|
|
466
|
+
- **`modular`**: Clean separation with modules (recommended for large projects)
|
|
467
|
+
- **`blueprints`**: Flask-style blueprints for organized route grouping
|
|
468
|
+
- **`microservices`**: Distributed service architecture with service discovery
|
|
469
|
+
- **`single`**: Single-file applications for simple projects and prototypes
|
|
470
|
+
|
|
471
|
+
### โก Component Generation
|
|
472
|
+
|
|
473
|
+
```bash
|
|
474
|
+
# Generate data models with type hints
|
|
475
|
+
wopr generate model User -f name:str -f email:str -f created_at:datetime -f is_active:bool
|
|
476
|
+
|
|
477
|
+
# Generate API endpoints with automatic routing
|
|
478
|
+
wopr generate endpoint /api/users -m GET --auth
|
|
479
|
+
wopr generate endpoint /api/users -m POST --auth
|
|
480
|
+
|
|
481
|
+
# Generate background tasks with categories
|
|
482
|
+
wopr generate task process_data --category data
|
|
483
|
+
wopr generate task send_notification --category notifications
|
|
484
|
+
|
|
485
|
+
# Generate WebSocket handlers
|
|
486
|
+
wopr generate websocket /ws/live --room-based
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### ๐ง Development Commands
|
|
490
|
+
|
|
491
|
+
```bash
|
|
492
|
+
# Run background task workers
|
|
493
|
+
wopr run-tasks --categories data,notifications --workers 3
|
|
494
|
+
|
|
495
|
+
# Clean up old completed tasks
|
|
496
|
+
wopr cleanup-tasks --days 7
|
|
497
|
+
wopr cleanup-tasks --months 1
|
|
498
|
+
|
|
499
|
+
# Version and system info
|
|
500
|
+
wopr version
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
### ๐ Generated Project Structure
|
|
504
|
+
|
|
505
|
+
**Modular Structure:**
|
|
506
|
+
```
|
|
507
|
+
my_app/
|
|
508
|
+
โโโ app.py # Main application
|
|
509
|
+
โโโ config.py # Configuration
|
|
510
|
+
โโโ requirements.txt # Dependencies
|
|
511
|
+
โโโ .env.example # Environment template
|
|
512
|
+
โโโ modules/ # Feature modules
|
|
513
|
+
โ โโโ auth/ # Authentication
|
|
514
|
+
โ โโโ api/ # API routes
|
|
515
|
+
โ โโโ admin/ # Admin interface
|
|
516
|
+
โ โโโ public/ # Public pages
|
|
517
|
+
โโโ models/ # Data models
|
|
518
|
+
โโโ tasks/ # Background tasks
|
|
519
|
+
โโโ templates/ # Jinja2 templates
|
|
520
|
+
โโโ static/ # CSS, JS, images
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
**Blueprint Structure:**
|
|
524
|
+
```
|
|
525
|
+
my_app/
|
|
526
|
+
โโโ app.py # Main application
|
|
527
|
+
โโโ blueprints/ # Route blueprints
|
|
528
|
+
โ โโโ auth.py # Auth routes
|
|
529
|
+
โ โโโ api.py # API routes
|
|
530
|
+
โ โโโ admin.py # Admin routes
|
|
531
|
+
โโโ ...
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### ๐ฏ Use Case Features
|
|
535
|
+
|
|
536
|
+
Each use case generates tailored code:
|
|
537
|
+
|
|
538
|
+
**Data Science:**
|
|
539
|
+
- Model training/inference endpoints
|
|
540
|
+
- Data processing pipelines
|
|
541
|
+
- Analytics and metrics APIs
|
|
542
|
+
- Jupyter notebook integration
|
|
543
|
+
|
|
544
|
+
**Real-time Dashboard:**
|
|
545
|
+
- WebSocket streaming endpoints
|
|
546
|
+
- Live data aggregation
|
|
547
|
+
- Chart and graph APIs
|
|
548
|
+
- Real-time metrics collection
|
|
549
|
+
|
|
550
|
+
**Content Management:**
|
|
551
|
+
- User authentication/authorization
|
|
552
|
+
- CRUD operations for content
|
|
553
|
+
- Media upload handling
|
|
554
|
+
- Admin dashboard interface
|
|
555
|
+
|
|
556
|
+
**Microservice:**
|
|
557
|
+
- Health check endpoints
|
|
558
|
+
- Service discovery integration
|
|
559
|
+
- Metrics and monitoring
|
|
560
|
+
- Minimal dependencies
|
|
561
|
+
|
|
562
|
+
## ๐ก๏ธ Security
|
|
563
|
+
|
|
564
|
+
- **CSRF Protection**: Built-in CSRF token generation and validation
|
|
565
|
+
- **Security Headers**: X-Frame-Options, CSP, HSTS, etc.
|
|
566
|
+
- **Rate Limiting**: Configurable rate limiting with decorators
|
|
567
|
+
- **Input Validation**: Request data validation and sanitization
|
|
568
|
+
- **Static File Security**: Path traversal protection
|
|
569
|
+
|
|
570
|
+
### JSON Limits (Size & Depth)
|
|
571
|
+
- Configure maximum JSON request body size via env `WOPR_JSON_MAX_BYTES` (bytes). If exceeded, returns HTTP 413 (Request too large).
|
|
572
|
+
- Configure maximum JSON nesting depth via env `WOPR_JSON_MAX_DEPTH`. If exceeded, returns HTTP 400 with a clear error.
|
|
573
|
+
- Limits are applied per-request; you can also set `request.max_body_bytes` / `request.max_json_depth` manually in middleware if needed.
|
|
574
|
+
|
|
575
|
+
### Secure Cookies (Production)
|
|
576
|
+
- When `ENV=production`, cookie attributes are enforced by default:
|
|
577
|
+
- `Secure=True`, `HttpOnly=True`, `SameSite=Lax` (if not set)
|
|
578
|
+
- To explicitly allow insecure cookies in production (not recommended), set `WOPR_ALLOW_INSECURE_COOKIES=true`.
|
|
579
|
+
- Gobstopper logs a warning when it has to override insecure cookie attributes in production.
|
|
580
|
+
|
|
581
|
+
### WebSocket Safety
|
|
582
|
+
- Max message size enforced via `MAX_WS_MESSAGE_BYTES` (default: 1 MiB). Oversized messages are closed with code 1009.
|
|
583
|
+
- Basic send backpressure with chunked writes (`WS_SEND_CHUNK_BYTES`, default: 64 KiB).
|
|
584
|
+
|
|
585
|
+
### Basic Rate Limiting
|
|
586
|
+
Use the built-in token-bucket limiter:
|
|
587
|
+
```python
|
|
588
|
+
from gobstopper.utils.rate_limiter import TokenBucketLimiter, rate_limit
|
|
589
|
+
|
|
590
|
+
limiter = TokenBucketLimiter(rate=5, capacity=10) # 5 req/sec, burst 10
|
|
591
|
+
|
|
592
|
+
@app.get('/limited')
|
|
593
|
+
@rate_limit(limiter, key=lambda req: req.client_ip)
|
|
594
|
+
async def limited(request):
|
|
595
|
+
return {'ok': True}
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
### Session Management
|
|
599
|
+
|
|
600
|
+
Gobstopper includes a production-grade, database-backed session system with a familiar API.
|
|
601
|
+
|
|
602
|
+
- **Pluggable Backends**: Supports Redis, PostgreSQL, and in-memory storage.
|
|
603
|
+
- **Secure by Default**: Optional HMAC-signed session IDs and secure cookie flags.
|
|
604
|
+
- **Ergonomic API**: Simple `request.session` access and `response.set_cookie()` helpers.
|
|
605
|
+
|
|
606
|
+
For more details, see the [Middleware documentation](./docs/core/middleware.md#session-management).
|
|
607
|
+
|
|
608
|
+
**Note**: The default file-based session storage is not recommended for production, especially in cloud or containerized environments. Use Redis or PostgreSQL for production deployments.
|
|
609
|
+
|
|
610
|
+
## โก Performance
|
|
611
|
+
|
|
612
|
+
- **RSGI Interface**: Maximum performance with Granian server
|
|
613
|
+
- **Async Throughout**: Non-blocking operations everywhere
|
|
614
|
+
- **Background Tasks**: Offload heavy work to background queues
|
|
615
|
+
- **Efficient Routing**: Fast path matching with parameter extraction
|
|
616
|
+
- **Optional Dependencies**: Load only what you need
|
|
617
|
+
|
|
618
|
+
## ๐งช Testing
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
# Install dev dependencies
|
|
622
|
+
uv sync --extra dev
|
|
623
|
+
|
|
624
|
+
# Run tests (when implemented)
|
|
625
|
+
uv run pytest
|
|
626
|
+
|
|
627
|
+
# Code quality
|
|
628
|
+
uv run black . # Format code
|
|
629
|
+
uv run ruff check . # Lint code
|
|
630
|
+
uv run mypy src/ # Type checking
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
## ๐ Documentation
|
|
634
|
+
|
|
635
|
+
### Official Documentation
|
|
636
|
+
|
|
637
|
+
Build and view the complete Sphinx documentation:
|
|
638
|
+
|
|
639
|
+
```bash
|
|
640
|
+
./build_docs.sh
|
|
641
|
+
cd sphinx-docs
|
|
642
|
+
python -m http.server 8080 -d build/html
|
|
643
|
+
# Visit http://localhost:8080
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
Or use live preview:
|
|
647
|
+
|
|
648
|
+
```bash
|
|
649
|
+
cd sphinx-docs
|
|
650
|
+
pip install -r requirements.txt
|
|
651
|
+
sphinx-autobuild source build/html
|
|
652
|
+
# Visit http://127.0.0.1:8000
|
|
653
|
+
```
|
|
654
|
+
|
|
655
|
+
### Additional Resources
|
|
656
|
+
|
|
657
|
+
- **Example Applications**: Fully commented examples demonstrating all features
|
|
658
|
+
- **Inline Documentation**: Comprehensive docstrings and type hints throughout
|
|
659
|
+
- **Markdown Docs**: Additional guides in the `docs/` directory
|
|
660
|
+
- See the [Changelog](CHANGELOG.md) for release notes
|
|
661
|
+
|
|
662
|
+
## ๐ ๏ธ Building from Source
|
|
663
|
+
|
|
664
|
+
Gobstopper includes Rust extensions for maximum performance. Build tools are provided:
|
|
665
|
+
|
|
666
|
+
```bash
|
|
667
|
+
# Install build dependencies
|
|
668
|
+
uv add --dev maturin build
|
|
669
|
+
|
|
670
|
+
# 1) Fast dev install of Rust core into your current venv (recommended while iterating)
|
|
671
|
+
# Defaults to features: router,templates,static
|
|
672
|
+
uv run python dev_install_rust.py --strip
|
|
673
|
+
# or explicitly:
|
|
674
|
+
MATURIN_FEATURES="router,templates,static" uv run python dev_install_rust.py --strip
|
|
675
|
+
|
|
676
|
+
# 2) Build wheels for the current platform (drops wheels in ./dist)
|
|
677
|
+
python build_wheels.py --platform local --features "router,templates,static"
|
|
678
|
+
|
|
679
|
+
# 3) Build Linux manylinux wheels for both x86_64 and aarch64 (requires Docker)
|
|
680
|
+
python build_wheels.py --platform linux --arch both --features "router,templates,static"
|
|
681
|
+
|
|
682
|
+
# 4) Build for all platforms
|
|
683
|
+
./build_linux_wheels.sh
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
To verify the Rust core is active at runtime, look for these logs on startup:
|
|
687
|
+
|
|
688
|
+
```
|
|
689
|
+
๐ Found Rust extensions, using high-performance router.
|
|
690
|
+
๐ฆ Rust template engine initialized successfully
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
You can also run:
|
|
694
|
+
|
|
695
|
+
```bash
|
|
696
|
+
python -c "import gobstopper._core as core; print('Symbols:', [s for s in dir(core) if not s.startswith('_')][:20])"
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
## ๐ฆ Distribution Packages
|
|
700
|
+
|
|
701
|
+
Pre-built wheels available for:
|
|
702
|
+
- **macOS ARM64**: Python 3.10, 3.11, 3.12, 3.13
|
|
703
|
+
- **Linux x86_64**: Python 3.10, 3.11, 3.12, 3.13
|
|
704
|
+
- **Linux ARM64**: Python 3.10, 3.11, 3.12, 3.13
|
|
705
|
+
- **Source Distribution**: Universal compatibility
|
|
706
|
+
|
|
707
|
+
## ๐ค Contributing
|
|
708
|
+
|
|
709
|
+
Gobstopper is built with modern Python and Rust:
|
|
710
|
+
- **Python 3.10+** (3.13 recommended) for latest async improvements
|
|
711
|
+
- **Rust** for high-performance components (optional)
|
|
712
|
+
- **Type hints** throughout the Python codebase
|
|
713
|
+
- **Modular architecture** for easy extension
|
|
714
|
+
- **Comprehensive error handling** and logging
|
|
715
|
+
- **Security-first design** with defense in depth
|
|
716
|
+
|
|
717
|
+
## ๐ License
|
|
718
|
+
|
|
719
|
+
MIT License - see LICENSE file for details.
|
|
720
|
+
|
|
721
|
+
## ๐ Links
|
|
722
|
+
|
|
723
|
+
- **GitHub**: https://github.com/gobstopper-framework/wopr
|
|
724
|
+
- **Documentation**: https://gobstopper-framework.readthedocs.io
|
|
725
|
+
- **PyPI**: https://pypi.org/project/wopr
|
|
726
|
+
|
|
727
|
+
---
|
|
728
|
+
|
|
729
|
+
**Gobstopper** - High-performance async web framework for modern Python web applications. ๐ฎ
|