patx/micropie
improve middleware examples. create new example showing restful explicit routing.
Commit b66abc2 · patx · 2025-06-11T22:30:26-04:00
Comments
No comments yet.
Diff
diff --git a/MicroPie.py b/MicroPie.py
index 3975bea..dd5299f 100644
--- a/MicroPie.py
+++ b/MicroPie.py
@@ -1,36 +1,20 @@
"""
-MicroPie: A simple Python ultra-micro web framework with ASGI
-support. https://patx.github.io/micropie
-
-Copyright 2025 Harrison Erd
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-1. Redistributions of source code must retain the above copyright notice,
- this list of conditions and the following disclaimer.
-
-2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
-3. Neither the name of the copyright holder nor the names of its
- contributors may be used to endorse or promote products derived from this
- software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
-IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
-THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
-EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
-OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
-OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
-EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+MicroPie: An ultra micro ASGI web framework.
+
+MicroPie is designed for lightweight web applications, offering
+a minimalistic API for building ASGI-compatible web services.
+It prioritizes simplicity and performance.
+
+Homepage and documentation: https://patx.github.io/micropie
+
+Copyright (c) 2025, Harrison Erd.
+License: BSD3 (see LICENSE for details)
"""
+__author__ = 'Harrison Erd'
+__version__ = '0.11-dev'
+__license__ = 'BSD3'
+
import asyncio
import contextvars
import inspect
@@ -284,7 +268,6 @@ class App:
if isinstance(request.get_json, dict):
request.body_params = {k: [str(v)] for k, v in request.get_json.items()}
except:
- print(f"Request error: {e}")
await self._send_response(send, 400, "400 Bad Request: Bad JSON")
return
elif "multipart/form-data" in content_type:
diff --git a/examples/hello_world/app.py b/examples/hello_world/app.py
index ceddd37..4287fc9 100644
--- a/examples/hello_world/app.py
+++ b/examples/hello_world/app.py
@@ -6,7 +6,7 @@ class Root(App):
async def index(self):
return 'Hello ASGI World!'
- async def greet(self,first_name='World', last_name=None):
+ async def greet(self, first_name='World', last_name=None):
if last_name:
return f'Hello {first_name} {last_name}'
return f'Hello {first_name}'
diff --git a/examples/middleware/router1.py b/examples/middleware/router.py
similarity index 100%
rename from examples/middleware/router1.py
rename to examples/middleware/router.py
diff --git a/examples/middleware/upload.py b/examples/middleware/upload.py
new file mode 100644
index 0000000..dddb263
--- /dev/null
+++ b/examples/middleware/upload.py
@@ -0,0 +1,48 @@
+"""
+This file demonstrates how to use a middleware to check file upload sizes
+before the request body is processed by the multipart parser.
+"""
+
+from MicroPie import App, HttpMiddleware
+
+MAX_UPLOAD_SIZE = 100 * 1024 * 1024 # 100MB
+
+class MaxUploadSizeMiddleware(HttpMiddleware):
+ async def before_request(self, request):
+ # Check if we're dealing with a POST, PUT, or PATCH request
+ if request.method in ("POST", "PUT", "PATCH"):
+ content_length = request.headers.get("content-length")
+ # Make sure the file is not too large
+ if int(content_length) > MAX_UPLOAD_SIZE:
+ return {
+ "status_code": 413,
+ "body": "413 Payload Too Large: Uploaded file exceeds size limit."
+ }
+ # If the check passes, return None to continue processing.
+ return None
+
+ async def after_request(self, request, status_code, response_body, extra_headers):
+ return None
+
+
+class FileUploadApp(App):
+ async def index(self):
+ """Serves an HTML form for file uploads."""
+ return """<html>
+<head><title>File Upload</title></head>
+<body>
+ <h2>Upload a File</h2>
+ <form action="/upload" method="post" enctype="multipart/form-data">
+ <input type="file" name="file"><br><br>
+ <input type="submit" value="Upload">
+ </form>
+</body>
+</html>"""
+
+ async def upload(self, file):
+ filename = file["filename"]
+ return filename
+
+
+app = FileUploadApp()
+app.middlewares.append(MaxUploadSizeMiddleware())
diff --git a/examples/rest/app.py b/examples/rest/app.py
new file mode 100644
index 0000000..2e11112
--- /dev/null
+++ b/examples/rest/app.py
@@ -0,0 +1,32 @@
+from micropie_rest import RESTApp, route
+
+class MyApp(RESTApp):
+ @route("/api/users/{user:str}/records/{record:int}", method=["GET", "HEAD"])
+ async def _get_record(self, user: str, record: int):
+ return {"user": user, "record": record}
+
+ @route("/api/users/{user:str}/records", method=["POST"])
+ async def _create_record(self, user: str):
+ try:
+ data = self.request.get_json
+ return {"user": user, "record": data.get("record_id"), "created": True}
+ except Exception:
+ return {"error": f"Invalid JSON"}
+
+ @route("/api/users/{user:str}/records/{record:int}/details/subdetails", method="GET")
+ async def _get_record_subdetails(self, user: str, record: int):
+ return {"user": user, "record": record, "subdetails": "more detailed info"}
+
+ # Implicitly routed (not using decorator)
+ async def records(self, user: str, record: str):
+ try:
+ record_id = int(record)
+ return {"user": user, "record": record_id, "implicit": True}
+ except ValueError:
+ return {"error": "Record must be an integer"}
+
+ # Private route, not exposed
+ async def _private(self):
+ return {"viewing": "private"}
+
+app = MyApp()
diff --git a/examples/middleware/router2.py b/examples/rest/micropie_rest.py
similarity index 75%
rename from examples/middleware/router2.py
rename to examples/rest/micropie_rest.py
index b5f3060..8050a18 100644
--- a/examples/middleware/router2.py
+++ b/examples/rest/micropie_rest.py
@@ -91,44 +91,17 @@ def route(path: str, method: Union[str, List[str]] = "GET"):
return handler
return decorator
-class MyApp(App):
+class RESTApp(App):
+ """A subclass of MicroPie.App that automatically registers routes using ExplicitRouter."""
def __init__(self):
super().__init__()
self.router = ExplicitRouter()
self.middlewares.append(self.router)
-
- # Automatically register routes from decorated methods
+ self._register_routes()
+
+ def _register_routes(self):
+ """Automatically register routes from decorated methods."""
for name, method in self.__class__.__dict__.items():
if hasattr(method, "_route"):
path, methods = method._route
self.router.add_route(path, getattr(self, name), methods)
-
- @route("/api/users/{user:str}/records/{record:int}", method=["GET", "HEAD"])
- async def _get_record(self, user: str, record: int):
- return {"user": user, "record": record}
-
- @route("/api/users/{user:str}/records", method=["POST"])
- async def _create_record(self, user: str):
- try:
- data = self.request.get_json
- return {"user": user, "record": data.get("record_id"), "created": True}
- except Exception as e:
- return {"error": f"Invalid JSON: {str(e)}"}
-
- @route("/api/users/{user:str}/records/{record:int}/details/subdetails", method="GET")
- async def _get_record_subdetails(self, user: str, record: int):
- return {"user": user, "record": record, "subdetails": "more detailed info"}
-
- # Implicitly routed (not using decorator)
- async def records(self, user: str, record: str):
- try:
- record_id = int(record)
- return {"user": user, "record": record_id, "implicit": True}
- except ValueError:
- return {"error": "Record must be an integer"}
-
- # Private route, not exposed
- async def _private(self):
- return {"viewing": "private"}
-
-app = MyApp()