pywebguard 1.0.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.
Files changed (68) hide show
  1. pywebguard-1.0.0/LICENSE +21 -0
  2. pywebguard-1.0.0/PKG-INFO +431 -0
  3. pywebguard-1.0.0/README.md +346 -0
  4. pywebguard-1.0.0/pywebguard/__init__.py +87 -0
  5. pywebguard-1.0.0/pywebguard/cli.py +1022 -0
  6. pywebguard-1.0.0/pywebguard/core/__init__.py +0 -0
  7. pywebguard-1.0.0/pywebguard/core/base.py +792 -0
  8. pywebguard-1.0.0/pywebguard/core/config.py +455 -0
  9. pywebguard-1.0.0/pywebguard/filters/__init__.py +0 -0
  10. pywebguard-1.0.0/pywebguard/filters/base.py +44 -0
  11. pywebguard-1.0.0/pywebguard/filters/ip_filter.py +233 -0
  12. pywebguard-1.0.0/pywebguard/filters/user_agent.py +102 -0
  13. pywebguard-1.0.0/pywebguard/frameworks/__init__.py +0 -0
  14. pywebguard-1.0.0/pywebguard/frameworks/_fastapi.py +191 -0
  15. pywebguard-1.0.0/pywebguard/frameworks/_flask.py +182 -0
  16. pywebguard-1.0.0/pywebguard/limiters/__init__.py +0 -0
  17. pywebguard-1.0.0/pywebguard/limiters/base.py +46 -0
  18. pywebguard-1.0.0/pywebguard/limiters/rate_limit.py +415 -0
  19. pywebguard-1.0.0/pywebguard/logging/__init__.py +0 -0
  20. pywebguard-1.0.0/pywebguard/logging/backends/__init__.py +17 -0
  21. pywebguard-1.0.0/pywebguard/logging/backends/_elasticsearch.py +307 -0
  22. pywebguard-1.0.0/pywebguard/logging/backends/_meilisearch.py +310 -0
  23. pywebguard-1.0.0/pywebguard/logging/backends/_mongodb.py +303 -0
  24. pywebguard-1.0.0/pywebguard/logging/base.py +117 -0
  25. pywebguard-1.0.0/pywebguard/logging/logger.py +408 -0
  26. pywebguard-1.0.0/pywebguard/security/__init__.py +0 -0
  27. pywebguard-1.0.0/pywebguard/security/base.py +44 -0
  28. pywebguard-1.0.0/pywebguard/security/cors.py +204 -0
  29. pywebguard-1.0.0/pywebguard/security/penetration.py +265 -0
  30. pywebguard-1.0.0/pywebguard/storage/__init__.py +0 -0
  31. pywebguard-1.0.0/pywebguard/storage/_mongodb.py +396 -0
  32. pywebguard-1.0.0/pywebguard/storage/_postgresql.py +539 -0
  33. pywebguard-1.0.0/pywebguard/storage/_redis.py +314 -0
  34. pywebguard-1.0.0/pywebguard/storage/_sqlite.py +374 -0
  35. pywebguard-1.0.0/pywebguard/storage/_tinydb.py +271 -0
  36. pywebguard-1.0.0/pywebguard/storage/base.py +327 -0
  37. pywebguard-1.0.0/pywebguard/storage/memory.py +245 -0
  38. pywebguard-1.0.0/pywebguard/utils/__init__.py +0 -0
  39. pywebguard-1.0.0/pywebguard/utils/ip.py +84 -0
  40. pywebguard-1.0.0/pywebguard/utils/request.py +65 -0
  41. pywebguard-1.0.0/pywebguard.egg-info/PKG-INFO +431 -0
  42. pywebguard-1.0.0/pywebguard.egg-info/SOURCES.txt +66 -0
  43. pywebguard-1.0.0/pywebguard.egg-info/dependency_links.txt +1 -0
  44. pywebguard-1.0.0/pywebguard.egg-info/entry_points.txt +2 -0
  45. pywebguard-1.0.0/pywebguard.egg-info/requires.txt +70 -0
  46. pywebguard-1.0.0/pywebguard.egg-info/top_level.txt +1 -0
  47. pywebguard-1.0.0/setup.cfg +4 -0
  48. pywebguard-1.0.0/setup.py +97 -0
  49. pywebguard-1.0.0/tests/__init__.py +1 -0
  50. pywebguard-1.0.0/tests/conftest.py +211 -0
  51. pywebguard-1.0.0/tests/test_cli.py +330 -0
  52. pywebguard-1.0.0/tests/test_core.py +304 -0
  53. pywebguard-1.0.0/tests/test_filters.py +260 -0
  54. pywebguard-1.0.0/tests/test_frameworks.py +130 -0
  55. pywebguard-1.0.0/tests/test_frameworks_fastapi.py +318 -0
  56. pywebguard-1.0.0/tests/test_frameworks_flask.py +179 -0
  57. pywebguard-1.0.0/tests/test_limiters.py +354 -0
  58. pywebguard-1.0.0/tests/test_security.py +330 -0
  59. pywebguard-1.0.0/tests/test_storage.py +206 -0
  60. pywebguard-1.0.0/tests/test_storage_memory.py +64 -0
  61. pywebguard-1.0.0/tests/test_storage_mongodb.py +265 -0
  62. pywebguard-1.0.0/tests/test_storage_postgresql.py +347 -0
  63. pywebguard-1.0.0/tests/test_storage_redis.py +145 -0
  64. pywebguard-1.0.0/tests/test_storage_sqlite.py +132 -0
  65. pywebguard-1.0.0/tests/test_storage_tinydb.py +110 -0
  66. pywebguard-1.0.0/tests/test_user_agent.py +182 -0
  67. pywebguard-1.0.0/tests/test_utils_ip.py +102 -0
  68. pywebguard-1.0.0/tests/test_utils_request.py +90 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 py-daily
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,431 @@
1
+ Metadata-Version: 2.1
2
+ Name: pywebguard
3
+ Version: 1.0.0
4
+ Summary: A comprehensive security library for Python web applications
5
+ Home-page: https://github.com/py-daily/pywebguard
6
+ Author: py-daily
7
+ Author-email: mm@ktechhub.com
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Topic :: Security
21
+ Classifier: Topic :: Internet :: WWW/HTTP
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/markdown
25
+ License-File: LICENSE
26
+ Requires-Dist: pydantic>=2.0.0
27
+ Requires-Dist: ipaddress>=1.0.23
28
+ Requires-Dist: python-dotenv>=0.19.0
29
+ Provides-Extra: fastapi
30
+ Requires-Dist: fastapi>=0.115.12; extra == "fastapi"
31
+ Requires-Dist: starlette>=0.14.0; extra == "fastapi"
32
+ Requires-Dist: uvicorn>=0.34.2; extra == "fastapi"
33
+ Provides-Extra: flask
34
+ Requires-Dist: flask>=3.1.1; extra == "flask"
35
+ Provides-Extra: redis
36
+ Requires-Dist: redis>=6.1.0; extra == "redis"
37
+ Requires-Dist: aioredis>=2.0.1; extra == "redis"
38
+ Provides-Extra: sqlite
39
+ Requires-Dist: aiosqlite>=0.21.0; extra == "sqlite"
40
+ Provides-Extra: tinydb
41
+ Requires-Dist: tinydb>=4.8.2; extra == "tinydb"
42
+ Provides-Extra: mongodb
43
+ Requires-Dist: pymongo>=4.13.0; extra == "mongodb"
44
+ Provides-Extra: postgresql
45
+ Requires-Dist: asyncpg>=0.30.0; extra == "postgresql"
46
+ Requires-Dist: psycopg2-binary>=2.9.10; extra == "postgresql"
47
+ Provides-Extra: elasticsearch
48
+ Requires-Dist: elasticsearch>=9.0.1; extra == "elasticsearch"
49
+ Provides-Extra: all-storage
50
+ Requires-Dist: redis>=6.1.0; extra == "all-storage"
51
+ Requires-Dist: aioredis>=2.0.1; extra == "all-storage"
52
+ Requires-Dist: aiosqlite>=0.21.0; extra == "all-storage"
53
+ Requires-Dist: tinydb>=4.8.2; extra == "all-storage"
54
+ Requires-Dist: pymongo>=4.13.0; extra == "all-storage"
55
+ Requires-Dist: asyncpg>=0.30.0; extra == "all-storage"
56
+ Requires-Dist: psycopg2-binary>=2.9.10; extra == "all-storage"
57
+ Requires-Dist: elasticsearch>=9.0.1; extra == "all-storage"
58
+ Provides-Extra: all-frameworks
59
+ Requires-Dist: fastapi>=0.115.12; extra == "all-frameworks"
60
+ Requires-Dist: starlette>=0.14.0; extra == "all-frameworks"
61
+ Requires-Dist: uvicorn>=0.34.2; extra == "all-frameworks"
62
+ Requires-Dist: flask>=3.1.1; extra == "all-frameworks"
63
+ Provides-Extra: all
64
+ Requires-Dist: fastapi>=0.115.12; extra == "all"
65
+ Requires-Dist: starlette>=0.14.0; extra == "all"
66
+ Requires-Dist: uvicorn>=0.34.2; extra == "all"
67
+ Requires-Dist: flask>=3.1.1; extra == "all"
68
+ Requires-Dist: redis>=6.1.0; extra == "all"
69
+ Requires-Dist: aioredis>=2.0.1; extra == "all"
70
+ Requires-Dist: aiosqlite>=0.21.0; extra == "all"
71
+ Requires-Dist: tinydb>=4.8.2; extra == "all"
72
+ Requires-Dist: pymongo>=4.13.0; extra == "all"
73
+ Requires-Dist: asyncpg>=0.30.0; extra == "all"
74
+ Requires-Dist: psycopg2-binary>=2.9.10; extra == "all"
75
+ Requires-Dist: elasticsearch>=9.0.1; extra == "all"
76
+ Provides-Extra: dev
77
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
78
+ Requires-Dist: pytest-cov>=6.1.1; extra == "dev"
79
+ Requires-Dist: pytest-asyncio>=0.26.0; extra == "dev"
80
+ Requires-Dist: black>=25.1.0; extra == "dev"
81
+ Requires-Dist: isort>=6.0.1; extra == "dev"
82
+ Requires-Dist: mypy>=1.15.0; extra == "dev"
83
+ Requires-Dist: flake8>=7.2.0; extra == "dev"
84
+
85
+
86
+ # PyWebGuard
87
+
88
+ A comprehensive security library for Python web applications, providing middleware for IP filtering, rate limiting, and other security features with both synchronous and asynchronous support.
89
+
90
+ For detailed installation instructions and configuration options, see [Installation Guide](docs/installation.md).
91
+
92
+ ## Key Features
93
+
94
+ - **IP Whitelisting and Blacklisting**: Control access based on IP addresses
95
+ - **User Agent Filtering**: Block requests from specific user agents
96
+ - **Rate Limiting**: Limit the number of requests from a single IP
97
+ - **Per-Route Rate Limiting**: Set different rate limits for different endpoints
98
+ - **Bulk Configuration**: Configure rate limits for multiple routes at once
99
+ - **Pattern Matching**: Use wildcards to apply rate limits to groups of routes
100
+ - **Automatic IP Banning**: Automatically ban IPs after a certain number of suspicious requests
101
+ - **Penetration Attempt Detection**: Detect and log potential penetration attempts
102
+ - **Custom Logging**: Log security events to a custom file
103
+ - **CORS Configuration**: Configure CORS settings for your web application
104
+ - **IP Geolocation**: Determine the country of an IP address
105
+ - **Flexible Storage**: Redis-enabled distributed storage, nosql, sql or in-memory storage
106
+ - **Async/Sync Support**: Works with both synchronous (Flask) and asynchronous (FastAPI) frameworks
107
+ - **Logging Backends**: Configurable logging backends with current support for Meilisearch
108
+
109
+
110
+
111
+ ## Quick Start
112
+
113
+ ```python
114
+ from fastapi import FastAPI
115
+ from pywebguard import FastAPIGuard, GuardConfig, RateLimitConfig
116
+ from pywebguard.storage.memory import AsyncMemoryStorage
117
+ from pywebguard.core.config import LoggingConfig
118
+
119
+ config = GuardConfig(
120
+ # IP filtering configuration
121
+ ip_filter={
122
+ "enabled": True,
123
+ "whitelist": ["127.0.0.1", "::1", "192.168.1.0/24"],
124
+ "blacklist": ["10.0.0.1", "172.16.0.0/16"],
125
+ },
126
+ # Global rate limiting configuration
127
+ rate_limit={
128
+ "enabled": True,
129
+ "requests_per_minute": 100,
130
+ "burst_size": 20,
131
+ "auto_ban_threshold": 200,
132
+ "auto_ban_duration": 3600, # 1 hour in seconds
133
+ },
134
+ # User agent filtering
135
+ user_agent={
136
+ "enabled": True,
137
+ "blocked_agents": ["curl", "wget", "Scrapy", "bot", "Bot"],
138
+ },
139
+ # CORS configuration
140
+ cors={
141
+ "enabled": True,
142
+ "allow_origins": ["http://localhost:3000", "https://example.com"],
143
+ "allow_methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
144
+ "allow_headers": ["Content-Type", "Authorization"],
145
+ "allow_credentials": True,
146
+ "max_age": 3600,
147
+ },
148
+ # Penetration detection
149
+ penetration={
150
+ "enabled": True,
151
+ "detect_sql_injection": True,
152
+ "detect_xss": True,
153
+ "detect_path_traversal": True,
154
+ "block_suspicious_requests": True,
155
+ },
156
+ # Logging configuration
157
+ logging={
158
+ "enabled": True,
159
+ "level": "DEBUG", # Change to DEBUG to see all messages
160
+ "log_blocked_requests": True,
161
+ "stream": True,
162
+ "stream_levels": ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
163
+ "meilisearch": {
164
+ "url": "https://meilisearch.dev.pydaily.com",
165
+ "api_key": os.getenv("MEILISEARCH_API_KEY"),
166
+ "index_name": "pywebguard",
167
+ },
168
+ },
169
+ )
170
+ # Define route-specific rate limits
171
+ route_rate_limits = [
172
+ {
173
+ "endpoint": "/api/limited",
174
+ "requests_per_minute": 5,
175
+ "burst_size": 2,
176
+ "auto_ban_threshold": 10,
177
+ "auto_ban_duration": 1800, # 30 minutes
178
+ },
179
+ {
180
+ "endpoint": "/api/uploads/*",
181
+ "requests_per_minute": 10,
182
+ "burst_size": 5,
183
+ "auto_ban_duration": 1800,
184
+ },
185
+ {
186
+ "endpoint": "/api/admin/**",
187
+ "requests_per_minute": 20,
188
+ "burst_size": 5,
189
+ "auto_ban_threshold": 50,
190
+ "auto_ban_duration": 7200, # 2 hours
191
+ },
192
+ ]
193
+
194
+
195
+ # Initialize storage (async in-memory for this example)
196
+ storage = AsyncMemoryStorage()
197
+
198
+ # Uncomment to use Redis storage instead or import any of the storage supports
199
+ # storage = AsyncRedisStorage(url="redis://localhost:6379")
200
+
201
+
202
+ # Add PyWebGuard middleware
203
+ app.add_middleware(
204
+ FastAPIGuard,
205
+ config=config,
206
+ storage=storage,
207
+ route_rate_limits=route_rate_limits,
208
+ )
209
+
210
+ Basic routes
211
+ @app.get("/", tags=["main"])
212
+ async def root():
213
+ """Root endpoint with default rate limit (100 req/min)"""
214
+ return {"message": "Hello World - Default rate limit (100 req/min)"}
215
+
216
+
217
+ @app.get("/api/limited", tags=["main"])
218
+ async def limited_endpoint():
219
+ """Strictly rate limited endpoint (5 req/min)"""
220
+ return {"message": "This endpoint is strictly rate limited (5 req/min)"}
221
+
222
+
223
+ @app.get("/api/uploads/files", tags=["main"])
224
+ async def upload_files():
225
+ """File upload endpoint with custom rate limit (10 req/min)"""
226
+ return {"message": "File upload endpoint with custom rate limit (10 req/min)"}
227
+
228
+
229
+ @app.get("/api/admin/dashboard", tags=["main"])
230
+ async def admin_dashboard():
231
+ """Admin dashboard with custom rate limit (20 req/min)"""
232
+ return {"message": "Admin dashboard with custom rate limit (20 req/min)"}
233
+
234
+
235
+ @app.get("/api/admin/users/list", tags=["main"])
236
+ async def admin_users():
237
+ """Admin users list with custom rate limit (20 req/min)"""
238
+ return {"message": "Admin users list with custom rate limit (20 req/min)"}
239
+
240
+
241
+ @app.get("/protected", tags=["main"])
242
+ async def protected(request: Request):
243
+ """Protected endpoint with default rate limit"""
244
+ return {
245
+ "message": "This is a protected endpoint with default rate limit",
246
+ "client_ip": request.client.host,
247
+ "user_agent": request.headers.get("user-agent", "Unknown"),
248
+ }
249
+
250
+
251
+ # Example of a path that might trigger penetration detection
252
+ @app.get("/search", tags=["main"])
253
+ async def search(q: str):
254
+ """
255
+ Search endpoint that might trigger penetration detection if malicious queries are used
256
+
257
+ Args:
258
+ q: Search query
259
+
260
+ Returns:
261
+ Search results
262
+ """
263
+ # PyWebGuard will check for SQL injection, XSS, etc. in the query parameter
264
+ return {"results": f"Search results for: {q}"}
265
+
266
+
267
+ # Helper endpoint to check remaining rate limits
268
+ @app.get("/rate-limit-status", tags=["helper"])
269
+ async def rate_limit_status(request: Request, path: str = "/"):
270
+ """
271
+ Check rate limit status for a specific path
272
+
273
+ Args:
274
+ request: The FastAPI request object
275
+ path: The path to check rate limits for (default: /)
276
+
277
+ Returns:
278
+ Rate limit information for the specified path
279
+ """
280
+ client_ip = request.client.host
281
+
282
+ # Get rate limit info for the specified path
283
+ guard = request.app.state.guard
284
+ rate_info = await guard.guard.rate_limiter.check_limit(client_ip, path)
285
+
286
+ return {
287
+ "path": path,
288
+ "allowed": rate_info["allowed"],
289
+ "remaining": rate_info["remaining"],
290
+ "reset": rate_info["reset"],
291
+ "client_ip": client_ip,
292
+ }
293
+
294
+
295
+ # Endpoint to check if an IP is banned
296
+ @app.get("/check-ban-status", tags=["helper"])
297
+ async def check_ban_status(request: Request, ip: Optional[str] = None):
298
+ """
299
+ Check if an IP is banned
300
+
301
+ Args:
302
+ request: The FastAPI request object
303
+ ip: The IP to check (default: client IP)
304
+
305
+ Returns:
306
+ Ban status information
307
+ """
308
+ check_ip = ip or request.client.host
309
+ guard = request.app.state.guard
310
+ is_banned = await guard.guard.is_ip_banned(check_ip)
311
+
312
+ return {
313
+ "ip": check_ip,
314
+ "is_banned": is_banned,
315
+ "timestamp": time.time(),
316
+ }
317
+
318
+
319
+ # Admin endpoint to get metrics
320
+ @app.get("/admin/metrics", tags=["admin"])
321
+ async def get_metrics(request: Request):
322
+ """
323
+ Get PyWebGuard metrics
324
+
325
+ Args:
326
+ request: The FastAPI request object
327
+
328
+ Returns:
329
+ Current metrics from PyWebGuard
330
+ """
331
+ # This would typically be protected by authentication
332
+ guard = request.app.state.guard
333
+ # Get rate limit info for all paths
334
+ rate_limits = {}
335
+ for path in ["/", "/api/limited", "/api/uploads/*", "/api/admin/**"]:
336
+ rate_info = await guard.guard.rate_limiter.check_limit(
337
+ request.client.host, path
338
+ )
339
+ rate_limits[path] = {
340
+ "allowed": rate_info["allowed"],
341
+ "remaining": rate_info["remaining"],
342
+ "reset": rate_info["reset"],
343
+ }
344
+
345
+ return {
346
+ "timestamp": time.time(),
347
+ "metrics": {
348
+ "rate_limits": rate_limits,
349
+ "client_ip": request.client.host,
350
+ "user_agent": request.headers.get("user-agent", "Unknown"),
351
+ },
352
+ }
353
+
354
+
355
+ # Admin endpoint to ban an IP
356
+ @app.post("/admin/ban-ip", tags=["admin"])
357
+ async def ban_ip(request: Request, ip: str, duration: int = 60):
358
+ """
359
+ Ban an IP address
360
+
361
+ Args:
362
+ request: The FastAPI request object
363
+ ip: The IP to ban
364
+ duration: Ban duration in seconds (default: 1 hour)
365
+
366
+ Returns:
367
+ Ban confirmation
368
+ """
369
+ # This would typically be protected by authentication
370
+ guard = request.app.state.guard
371
+ ban_key = f"banned_ip:{ip}"
372
+ await guard.guard.storage.set(
373
+ ban_key,
374
+ {"reason": "Manually banned via API", "timestamp": time.time()},
375
+ duration,
376
+ )
377
+
378
+ return {
379
+ "message": f"IP {ip} banned for {duration} seconds",
380
+ "timestamp": time.time(),
381
+ }
382
+
383
+
384
+ # Admin endpoint to unban an IP
385
+ @app.post("/admin/unban-ip", tags=["admin"])
386
+ async def unban_ip(request: Request, ip: str):
387
+ """
388
+ Unban an IP address
389
+
390
+ Args:
391
+ request: The FastAPI request object
392
+ ip: The IP to unban
393
+
394
+ Returns:
395
+ Unban confirmation
396
+ """
397
+ # This would typically be protected by authentication
398
+ guard = request.app.state.guard
399
+ ban_key = f"banned_ip:{ip}"
400
+ await guard.guard.storage.delete(ban_key)
401
+
402
+ return {
403
+ "message": f"IP {ip} unbanned",
404
+ "timestamp": time.time(),
405
+ }
406
+
407
+
408
+ if __name__ == "__main__":
409
+ logger.info("Starting PyWebGuard FastAPI example server...")
410
+ uvicorn.run(app, host="0.0.0.0", port=8000)
411
+ ```
412
+
413
+
414
+ ## Documentation
415
+
416
+ - [Installation Guide](docs/installation.md)
417
+ - [CLI Usage](docs/cli.md)
418
+ - [Core Features](docs/core/)
419
+ - [Framework Integration](docs/frameworks/)
420
+ - [Storage Backends](docs/storage/)
421
+
422
+ ## Contributors
423
+
424
+ <a href="https://github.com/py-daily/pywebguard/graphs/contributors">
425
+ <img src="https://contrib.rocks/image?repo=py-daily/pywebguard" width="800" />
426
+ </a>
427
+
428
+ ## License
429
+
430
+ This project is licensed under the [MIT License](LICENSE).
431
+