improve middleware examples. create new example showing restful explicit routing.

Commit b66abc2 · patx · 2025-06-11T22:30:26-04:00

Changeset
b66abc256efb7a264f4574f6bf801af044f2ec6e
Parents
ea35a0e0eb2d445045a00c2eff88e12f148cd320

View source at this commit

Comments

No comments yet.

Log in to comment

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()