patx/micropie
dropped WSGI support, no more run() method, full ASGI support, basic support for websockets
Commit 9334780 · patx · 2025-01-27T23:58:31-05:00
Comments
No comments yet.
Diff
diff --git a/MicroPie.py b/MicroPie.py
index ce51ae4..2f92931 100644
--- a/MicroPie.py
+++ b/MicroPie.py
@@ -1,5 +1,5 @@
"""
-MicroPie: A simple Python ultra-micro web framework with WSGI
+MicroPie: A simple Python ultra-micro web framework with ASGI
support. https://patx.github.io/micropie
Copyright Harrison Erd
@@ -31,7 +31,6 @@ OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
-from wsgiref.simple_server import make_server
import time
import uuid
import inspect
@@ -39,7 +38,6 @@ import os
import mimetypes
from urllib.parse import parse_qs
from typing import Optional, Dict, Any, Union, Tuple, List
-import asyncio
try:
from jinja2 import Environment, FileSystemLoader
@@ -51,344 +49,6 @@ except ImportError:
class Server:
SESSION_TIMEOUT: int = 8 * 3600 # 8 hours
- def __init__(self) -> None:
- if JINJA_INSTALLED:
- self.env = Environment(loader=FileSystemLoader("templates"))
-
- self.sessions: Dict[str, Dict[str, Any]] = {}
- self.query_params: Dict[str, List[str]] = {}
- self.body_params: Dict[str, List[str]] = {}
- self.path_params: List[str] = []
- self.session: Dict[str, Any] = {}
- self.environ: Optional[Dict[str, Any]] = None
- self.start_response: Optional[Any] = None
-
- def run(self, host: str = "127.0.0.1", port: int = 8080) -> None:
- print(f"Serving on http://{host}:{port}")
- with make_server(host, port, self.wsgi_app) as httpd:
- try:
- httpd.serve_forever()
- except KeyboardInterrupt:
- print("\nShutting down server...")
-
- def get_session(self, request_handler: Any) -> Dict[str, Any]:
- cookie = request_handler.headers.get("Cookie")
- session_id = None
-
- if cookie:
- cookies = {
- item.split("=")[0].strip(): item.split("=")[1].strip()
- for item in cookie.split(";")
- }
- session_id = cookies.get("session_id")
-
- if not session_id or session_id not in self.sessions:
- session_id = str(uuid.uuid4())
- self.sessions[session_id] = {"last_access": time.time()}
- request_handler.send_response(200)
- request_handler.send_header(
- "Set-Cookie", f"session_id={session_id}; Path=/; HttpOnly; SameSite=Strict"
- )
- request_handler.end_headers()
-
- session = self.sessions.get(session_id)
- if session:
- session["last_access"] = time.time()
- else:
- session = {"last_access": time.time()}
- self.sessions[session_id] = session
-
- return session
-
- def cleanup_sessions(self) -> None:
- now = time.time()
- self.sessions = {
- sid: data
- for sid, data in self.sessions.items()
- if data.get("last_access", now) + self.SESSION_TIMEOUT > now
- }
-
- def redirect(self, location: str) -> Tuple[int, str]:
- return (
- 302,
- (
- "<html><head>"
- f"<meta http-equiv='refresh' content='0;url={location}'>"
- "</head></html>"
- ),
- )
-
- def render_template(self, name: str, **kwargs: Any) -> str:
- if not JINJA_INSTALLED:
- raise ImportError("Jinja2 is not installed.")
- return self.env.get_template(name).render(kwargs)
-
- def serve_static(self, filepath: str) -> Union[Tuple[int, str], Tuple[int, bytes, List[Tuple[str, str]]]]:
- safe_root = os.path.abspath("static")
- requested_file = os.path.abspath(os.path.join("static", filepath))
- if not requested_file.startswith(safe_root):
- return 403, "403 Forbidden"
- if not os.path.isfile(requested_file):
- return 404, "404 Not Found"
- content_type, _ = mimetypes.guess_type(requested_file)
- if not content_type:
- content_type = "application/octet-stream"
- with open(requested_file, "rb") as f:
- content = f.read()
- return 200, content, [("Content-Type", content_type)]
-
- def validate_request(self, method: str) -> bool:
- try:
- if method == "GET":
- for key, value in self.query_params.items():
- if (
- not isinstance(key, str)
- or not all(isinstance(v, str) for v in value)
- ):
- print(f"Invalid query parameter: {key} -> {value}")
- return False
-
- if method == "POST":
- for key, value in self.body_params.items():
- if (
- not isinstance(key, str)
- or not all(isinstance(v, str) for v in value)
- ):
- print(f"Invalid body parameter: {key} -> {value}")
- return False
-
- return True
- except Exception as e:
- print(f"Error during request validation: {e}")
- return False
-
- def wsgi_app(self, environ: Dict[str, Any], start_response: Any) -> List[bytes]:
- self.environ = environ
- self.start_response = start_response
-
- path = environ["PATH_INFO"].strip("/")
- method = environ["REQUEST_METHOD"]
-
- path_parts = path.split("/") if path else []
- func_name = path_parts[0] if path_parts else "index"
- self.path_params = path_parts[1:] if len(path_parts) > 1 else []
-
- handler_function = getattr(self, func_name, None)
-
- if not handler_function:
- self.path_params = path_parts
- handler_function = getattr(self, "index", None)
-
- self.query_params = parse_qs(environ.get("QUERY_STRING", ""))
-
-
- class MockRequestHandler:
- def __init__(self, environ: Dict[str, Any]) -> None:
- self.environ = environ
- self.headers = {
- key[5:].replace("_", "-").lower(): value
- for key, value in environ.items()
- if key.startswith("HTTP_")
- }
- self.cookies = self._parse_cookies()
- self._headers_to_send: List[Tuple[str, str]] = []
-
- def _parse_cookies(self) -> Dict[str, str]:
- cookies = {}
- if "HTTP_COOKIE" in self.environ:
- cookie_header = self.environ["HTTP_COOKIE"]
- for cookie in cookie_header.split(";"):
- if "=" in cookie:
- k, v = cookie.strip().split("=", 1)
- cookies[k] = v
- return cookies
-
- def send_response(self, code: int) -> None:
- pass
-
- def send_header(self, key: str, value: str) -> None:
- self._headers_to_send.append((key, value))
-
- def end_headers(self) -> None:
- pass
-
- request_handler = MockRequestHandler(environ)
-
- session_id = request_handler.cookies.get("session_id")
- if session_id and session_id in self.sessions:
- self.session = self.sessions[session_id]
- self.session["last_access"] = time.time()
- else:
- session_id = str(uuid.uuid4())
- self.session = {"last_access": time.time()}
- self.sessions[session_id] = self.session
- request_handler.send_header(
- "Set-Cookie", f"session_id={session_id}; Path=/; HttpOnly; SameSite=Strict;"
- )
-
- self.request = method
- self.body_params = {}
- self.files = {}
-
- if method == "POST":
- try:
- content_type = environ.get("CONTENT_TYPE", "")
- content_length = int(environ.get("CONTENT_LENGTH", 0) or 0)
- body = environ["wsgi.input"].read(content_length)
-
- if "multipart/form-data" in content_type:
- self.parse_multipart(body, content_type)
- else:
- body_str = body.decode("utf-8", "ignore")
- self.body_params = parse_qs(body_str)
- except Exception as e:
- start_response("400 Bad Request", [("Content-Type", "text/html")])
- return [f"400 Bad Request: {str(e)}".encode("utf-8")]
-
- sig = inspect.signature(handler_function)
- func_args = []
-
- for param in sig.parameters.values():
- if self.path_params:
- func_args.append(self.path_params.pop(0))
- elif param.name in self.query_params:
- func_args.append(self.query_params[param.name][0])
- elif param.name in self.body_params:
- func_args.append(self.body_params[param.name][0])
- elif param.name in self.files:
- func_args.append(self.files[param.name])
- elif param.name in self.session:
- func_args.append(self.session[param.name])
- elif param.default is not param.empty:
- func_args.append(param.default)
- else:
- msg = f"400 Bad Request: Missing required parameter '{param.name}'"
- start_response("400 Bad Request", [("Content-Type", "text/html")])
- return [msg.encode("utf-8")]
-
- if handler_function == getattr(self, "index", None) and not func_args and path:
- start_response("404 Not Found", [("Content-Type", "text/html")])
- return [b"404 Not Found"]
-
- try:
- response = handler_function(*func_args)
- status_code = 200
- response_body = response
- extra_headers = []
-
- if isinstance(response, tuple):
- if len(response) == 2:
- status_code, response_body = response
- elif len(response) == 3:
- status_code, response_body, extra_headers = response
- else:
- start_response("500 Internal Server Error", [("Content-Type", "text/html")])
- return [b"500 Internal Server Error: Invalid response tuple"]
-
- status_map = {
- 206: "206 Partial Content",
- 302: "302 Found",
- 404: "404 Not Found",
- 500: "500 Internal Server Error",
- }
- status_str = status_map.get(status_code, f"{status_code} OK")
- headers = request_handler._headers_to_send
- headers.extend(extra_headers)
- if not any(h[0].lower() == "content-type" for h in headers):
- headers.append(("Content-Type", "text/html; charset=utf-8"))
-
- start_response(status_str, headers)
-
- if hasattr(response_body, "__iter__") and not isinstance(response_body, (bytes, str)):
- def byte_stream(gen: Any) -> Any:
- for chunk in gen:
- if isinstance(chunk, str):
- yield chunk.encode("utf-8")
- else:
- yield chunk
- return byte_stream(response_body)
-
- if isinstance(response_body, str):
- response_body = response_body.encode("utf-8")
-
- return [response_body]
-
- except Exception as e:
- print(f"Error processing request: {e}")
- try:
- start_response("500 Internal Server Error", [("Content-Type", "text/html")])
- except:
- pass
- return [b"500 Internal Server Error"]
-
- def parse_multipart(self, body: bytes, content_type: str) -> None:
- boundary = None
- parts = content_type.split(";")
- for part in parts:
- part = part.strip()
- if part.startswith("boundary="):
- boundary = part.split("=", 1)[1]
- break
-
- if not boundary:
- raise ValueError("Boundary not found in Content-Type header.")
-
- boundary_bytes = boundary.encode("utf-8")
- delimiter = b'--' + boundary_bytes
- end_delimiter = b'--' + boundary_bytes + b'--'
-
- sections = body.split(delimiter)
- for section in sections:
- if not section or section == b'--' or section == b'--\r\n':
- continue
- if section.startswith(b'\r\n'):
- section = section[2:]
- if section.endswith(b'\r\n'):
- section = section[:-2]
- if section == b'--':
- continue
-
- try:
- headers, content = section.split(b'\r\n\r\n', 1)
- except ValueError:
- continue
-
- headers = headers.decode("utf-8", "ignore").split("\r\n")
- header_dict = {}
- for header in headers:
- if ':' in header:
- key, value = header.split(':', 1)
- header_dict[key.strip().lower()] = value.strip()
-
- disposition = header_dict.get("content-disposition", "")
- disposition_parts = disposition.split(";")
- disposition_dict = {}
- for disp_part in disposition_parts:
- if "=" in disp_part:
- key, value = disp_part.strip().split("=", 1)
- disposition_dict[key] = value.strip('"')
-
- name = disposition_dict.get("name")
- filename = disposition_dict.get("filename")
-
- if filename:
- file_content_type = header_dict.get("content-type", "application/octet-stream")
- file_data = content
- self.files[name] = {
- 'filename': filename,
- 'content_type': file_content_type,
- 'data': file_data
- }
- elif name:
- value = content.decode("utf-8", "ignore")
- if name in self.body_params:
- self.body_params[name].append(value)
- else:
- self.body_params[name] = [value]
-
-class AsyncServer:
- SESSION_TIMEOUT: int = 8 * 3600 # 8 hours
-
def __init__(self) -> None:
if JINJA_INSTALLED:
self.env = Environment(loader=FileSystemLoader("templates"))
@@ -409,6 +69,7 @@ class AsyncServer:
if scope["type"] == "websocket":
# Example approach for route-based WebSocket handler lookup:
path = scope["path"].lstrip("/")
+ self.scope = scope
path_parts = path.split("/") if path else []
func_name = path_parts[0] if path_parts else "default"
self.path_params = path_parts[1:] if len(path_parts) > 1 else []
@@ -428,6 +89,7 @@ class AsyncServer:
return
elif scope["type"] == "http":
+ self.scope = scope
method = scope["method"]
path = scope["path"].lstrip("/")
path_parts = path.split("/") if path else []
diff --git a/README.md b/README.md
index 4a3701a..d2e6023 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
## **Introduction**
-**MicroPie** is a lightweight Python web framework that makes building web applications simple and efficient. It includes features such as routing, session management, WSGI support, and Jinja2 template rendering.
+**MicroPie** is a lightweight Python web framework that makes building web applications simple and efficient. It includes features such as routing, session management, ASGI support, and Jinja2 template rendering.
### **Key Features**
*"Fast, efficient, and deliciously simple."*
@@ -12,7 +12,7 @@
- 🔐 **Sessions:** Simple session management using cookies.
- 🎨 **Templates:** Jinja2 for dynamic HTML pages.
- ⚡ **Fast & Lightweight:** No unnecessary dependencies. Life’s too short for bloated frameworks.
-- 🖥️ **WSGI support:** Deploy with WSGI servers like gunicorn making web development easy as... pie!
+- 🖥️ **ASGI support:** Deploy with any ASGI server, like **uvicorn** making web development easy as... pie!
## **Installing MicroPie**
### **Normal Installation**
@@ -22,12 +22,11 @@ pip install micropie
```
This will install MicroPie along with `jinja2` as a dependency, enabling the built-in `render_template` method. This is the recommended way to install this framework.
-### **Minimal Installation**
-If you prefer an ultra-minimalistic setup, you can run MicroPie without installing Jinja. Simply download the standalone script:
-
-[MicroPie.py](https://raw.githubusercontent.com/patx/micropie/refs/heads/main/MicroPie.py)
-
-Place the script in your project directory, and you're good to go. Please note you will not be able to use the `render_template` method. It will raise an `ImportError`, unless you have Jinja installed via `pip install jinja2`.
+To run your application you need an ASGI web server, like **uvicorn**. Install it with:
+```bash
+pip install uvicorn
+```
+MicroPie will also work with any ASGI server of your choice!
## **Getting Started**
@@ -40,16 +39,16 @@ class MyApp(Server):
def index(self, name="Guest"):
return f"Hello, {name}!"
-MyApp().run()
+app = MyApp()
```
Run the server:
```bash
-python app.py
+uvicorn app:app
```
-Visit your app at [http://127.0.0.1:8080](http://127.0.0.1:8080).
+Visit your app at [http://127.0.0.1:8000](http://127.0.0.1:8000). In MicroPie your application code can look just like the WSGI code you are used to writing!
## **Core Features**
@@ -60,6 +59,12 @@ class MyApp(Server):
def hello(self):
return "Hello, world!"
```
+Your methods can also, of course, be `async` whenever needed:
+```python
+class MyApp(Server):
+ async def hello(self):
+ return "Hello World!"
+```
**Access:**
- Basic route: [http://127.0.0.1:8080/hello](http://127.0.0.1:8080/hello)
@@ -146,34 +151,7 @@ class MyApp(Server):
return f"Registered {username} with email {email}"
```
-### 4. **WSGI Support**
-MicroPie includes built-in WSGI support via the wsgi_app() method, allowing you to deploy your applications with WSGI-compatible servers like Gunicorn.
-
-#### **Example**
-Create a file named app.py:
-```python
-from MicroPie import Server
-
-class MyApp(Server):
- def index(self):
- return "Hello, WSGI World!"
-
-app = MyApp()
-wsgi_application = app.wsgi_app
-```
-
-Run `app.py` with:
-```bash
-gunicorn app:wsgi_application
-```
-
-#### Why Use WSGI?
-WSGI (Web Server Gateway Interface) is the standard Python interface between web servers and web applications. Deploying with a WSGI server like Gunicorn provides benefits such as:
-- Better Performance: Multi-threaded and multi-process capabilities.
-- Scalability: Easily handle multiple requests concurrently.
-- Production Readiness: Designed for high-load environments.
-
-### **5. Handling Sessions**
+### **4. Handling Sessions**
MicroPie has built in session handling:
```python
class MyApp(Server):
@@ -187,10 +165,10 @@ class MyApp(Server):
return f"Welcome! You have visited this page {self.session['visits']} times."
-MyApp().run()
+app = MyApp() # Run with `uvicorn app:app` assuming this file saved as `app.py`
```
-### **6. Jinja2 Built In**
+### **5. Jinja2 Built In**
MicroPie has Jinja template engine built in. You can use it with the `render_template` method. You can also implement any other template engine you would like.
#### **`app.py`**
@@ -201,7 +179,7 @@ class MyApp(Server):
# Pass data to the template for rendering
return self.render_template("index.html", title="Welcome", message="Hello from MicroPie!")
-MyApp().run(port=8080)
+app = MyApp() # Run with `uvicorn app:app`
```
#### **HTML**
@@ -221,7 +199,7 @@ In order to use the `render_template` method you must put your HTML template fil
</html>
```
-### **7. Serving Static Files**
+### **6. Serving Static Files**
MicroPie can serve static files (such as CSS, JavaScript, and images) from a static directory using the built in `serve_static` method. To do this you must define a route you would like to serve your static files from. For example:
```python
class Root(Server):
@@ -230,7 +208,7 @@ class Root(Server):
```
#### **Setup**
-- Create a directory named `static` in the same location as your MicroPie application. For saftey the `serve_static` method will only work if `filename` is in the `static` directory.
+- Create a directory named `static` in the same location as your MicroPie application. For safety the `serve_static` method will only work if `filename` is in the `static` directory.
- Place your static files (e.g., style.css, script.js, logo.png) inside the static directory.
#### **Accessing Static Files**
@@ -239,18 +217,12 @@ Static files can be accessed via the `/static/` URL path. For example, if you ha
http://127.0.0.1:8080/static/style.css
```
-### **8. Streaming Responses and WebSockets**
-
-#### **Streaming Response Support**
+### **7. Streaming Responses**
MicroPie provides support for streaming responses, allowing you to send data to the client in chunks instead of all at once. This is particularly useful for scenarios where data is generated or processed over time, such as live feeds, large file downloads, or incremental data generation.
-#### How It Works
-Streaming is supported through the `wsgi_app` method, making it compatible with WSGI servers like **Gunicorn** or the built in `run` method. When a route handler returns a generator or an iterable (excluding strings), MicroPie automatically streams the response to the client.
-
With the following saved as `app.py`:
```python
import time
-from MicroPie import Server
class Root(Server):
@@ -262,31 +234,44 @@ class Root(Server):
return generator()
app = Root()
-wsgi_app = app.wsgi_app
```
-Run your application with `gunicorn app:wsgi_app`. For best performance with streaming, consider tuning Gunicorn settings such as worker types (e.g., `--worker-class gevent`) to handle long-lived connections efficiently.
-#### **WebSockets Integration**
-MicroPie applications can seamlessly integrate **WebSockets** by running a separate WebSocket server using Python’s `websockets` library. This enables real-time, bidirectional communication between clients and the server, independent of the HTTP server being used. To get started with WebSockets in MicroPie, ensure you have the `websockets` package installed `pip install websockets`.
-#### How It Works
+### **8. WebSockets**
+MicroPie offers extremely basic built-in WebSocket support for real-time communication. WebSocket routes are defined with methods starting with `websocket_`. Create a simple WebSocket echo server:
+```python
+class MyApp(Server):
+ async def websocket_echo(self, scope, receive, send):
+ await send({"type": "websocket.accept"})
+ try:
+ while True:
+ message = await receive()
+ if message["type"] == "websocket.receive":
+ await send({"type": "websocket.send", "text": message["text"]})
+ elif message["type"] == "websocket.disconnect":
+ break
+ except Exception as e:
+ print(f"WebSocket error: {e}")
+ await send({"type": "websocket.close", "code": 1011})
-- **The HTTP server (MicroPie)** handles regular web requests and serves the frontend.
-- **A separate WebSocket server** runs concurrently to handle real-time communication.
-- Clients connect to the WebSocket server via the frontend and exchange messages asynchronously.
-- **Threading Considerations:** Since the WebSocket server runs in a separate thread, developers should handle shared resources carefully to avoid concurrency issues.
-- **Port Management:** The WebSocket server must run on a different port than the HTTP server to avoid conflicts.
-- **Client Compatibility:** Ensure that clients support WebSockets when implementing features relying on real-time communication.
+app = MyApp()
+```
+
+Save the above code as app.py, then run it with uvicorn:
+```python
+uvicorn app:app
+```
+Connect to the WebSocket server using a WebSocket client (e.g., websocat):
+```python
+websocat ws://127.0.0.1:8000/echo
+```
+Type messages in the client to see the server echo them back in real time.
-**For a full example showing `websockets` and MicroPie check out the [chatroom example](https://github.com/patx/micropie/tree/main/examples/chatroom).**
## **API Reference**
### Class: Server
-#### run(host='127.0.0.1', port=8080)
-Starts the WSGI server with the specified host and port.
-
#### get_session(request_handler)
Retrieves or creates a session for the current request. Sessions are managed via cookies.
@@ -299,17 +284,18 @@ Returns a 302 redirect response to the specified URL.
#### render_template(name, **args)
Renders a Jinja2 template with provided context variables.
-#### validate_request(method)
-Validates incoming requests for both GET and POST methods based on query and body parameters.
-
-#### wsgi_app(environ, start_response)
-WSGI-compliant method for parsing requests and returning responses. Ideal for production deployment using WSGI servers.
-
#### serve_static(filename)
Serve static files from the `static` directory.
## **Examples**
-Check out the [examples folder](https://github.com/patx/micropie/tree/main/examples) for more advanced usage, including template rendering, custom HTTP request handling, file uploads, session usage, websockets, streaming and form handling.
+Check out the [examples folder](https://github.com/patx/micropie/tree/main/examples) for more advanced usage, including:
+- Template rendering
+- Custom HTTP request handling
+- File uploads
+- Session usage
+- Websockets
+- Streaming
+- Form handling.
## **Feature Comparison**
@@ -320,14 +306,12 @@ Check out the [examples folder](https://github.com/patx/micropie/tree/main/examp
| **Template Engine** | Jinja2 | Jinja2 | None | SimpleTpl | Django Templating | Jinja2 |
| **Session Handling**| Built-in | Extension | Built-in | Plugin | Built-in | Extension |
| **Request Handling**| Simple | Flexible | Advanced | Simple | Advanced | Advanced |
-| **Performance** | High [^1] | High | Moderate | High | Moderate | Very High |
-| **WSGI Support** | Yes | Yes | Yes | Yes | Yes | No (ASGI) |
-| **Async Support** | No | No (Quart) | No | No | Limited | Yes |
+| **Performance** | High | High | Moderate | High | Moderate | Very High |
+| **WSGI Support** | No (ASGI) | Yes | Yes | Yes | Yes | No (ASGI) |
+| **Async Support** | Yes | No (Quart) | No | No | Limited | Yes |
| **Deployment** | Simple | Moderate | Moderate | Simple | Complex | Moderate |
-[^1]: *Note that while MicroPie is high-performing for lightweight applications, it may not scale well for complex, high-traffic web applications due to the lack of advanced features such as asynchronous request handling and database connection pooling, which are found in frameworks like Django and Flask. To achieve similar performance with MicroPie use `gunicorn` with `gevent`.*
-
## **Suggestions or Feedback?**
We welcome suggestions, bug reports, and pull requests!
- File issues or feature requests [here](https://github.com/patx/micropie/issues).
diff --git a/examples/AsyncServer/HelloWorld/app.py b/examples/AsyncServer/HelloWorld/app.py
deleted file mode 100644
index ba5a603..0000000
--- a/examples/AsyncServer/HelloWorld/app.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from MicroPie import AsyncServer
-
-
-class Root(AsyncServer):
-
- async def index(self):
- return 'Hello ASGI World!'
-
- async def hello(self, name="ASGI"):
- return f'Hello {name}!'
-
-app = Root() # Run with `uvicorn app:app`
diff --git a/examples/Server/file_uploads/app.py b/examples/file_uploads/app.py
similarity index 93%
rename from examples/Server/file_uploads/app.py
rename to examples/file_uploads/app.py
index ff094f0..ba17aaf 100644
--- a/examples/Server/file_uploads/app.py
+++ b/examples/file_uploads/app.py
@@ -59,6 +59,3 @@ class Root(Server):
app = Root()
-wsgi_app = app.wsgi_app # Run with `gunicorn app:wsgi_app`
-if __name__ == "__main__":
- app.run() # Run with `python3 app.py`
diff --git a/examples/Server/headers/app.py b/examples/headers/app.py
similarity index 78%
rename from examples/Server/headers/app.py
rename to examples/headers/app.py
index 4746d1d..34ccae6 100644
--- a/examples/Server/headers/app.py
+++ b/examples/headers/app.py
@@ -16,6 +16,3 @@ class Root(Server):
app = Root()
-wsgi_app = app.wsgi_app # Run with `gunicorn text:wsgi_app`
-if __name__ == "__main__":
- app.run() # Run with `python3 app.py`
diff --git a/examples/hello_world/app.py b/examples/hello_world/app.py
new file mode 100644
index 0000000..1ded149
--- /dev/null
+++ b/examples/hello_world/app.py
@@ -0,0 +1,11 @@
+from MicroPie import Server
+
+
+class Root(Server):
+
+ def index(self, name=None):
+ if name:
+ return f'Hello {name}'
+ return 'Hello ASGI World!'
+
+app = Root() # Run with `uvicorn app:app`
diff --git a/examples/Server/pastebin/app.py b/examples/pastebin/app.py
similarity index 59%
rename from examples/Server/pastebin/app.py
rename to examples/pastebin/app.py
index beb68c3..97ca3af 100644
--- a/examples/Server/pastebin/app.py
+++ b/examples/pastebin/app.py
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
"""
- A simple no frills pastebin using MicroPie, pickleDB, and highlight.js.
+A simple no frills pastebin using MicroPie, pickleDB, and highlight.js.
"""
from uuid import uuid4
@@ -9,14 +9,13 @@ from MicroPie import Server
from pickledb import PickleDB
from markupsafe import escape
-
db = PickleDB('pastes.db')
-
class Root(Server):
- def index(self):
- if self.request == 'POST':
+ async def index(self):
+ # Check the HTTP method from the ASGI scope
+ if self.scope["method"] == "POST":
paste_content = self.body_params.get('paste_content', [''])[0]
pid = str(uuid4())
db.set(pid, escape(paste_content))
@@ -24,17 +23,16 @@ class Root(Server):
return self.redirect(f'/paste/{pid}')
return self.render_template('index.html')
- def paste(self, paste_id, delete=None):
+ async def paste(self, paste_id, delete=None):
if delete == 'delete':
db.remove(paste_id)
db.save()
return self.redirect('/')
- return self.render_template('paste.html', paste_id=paste_id,
- paste_content=db.get(paste_id))
-
+ return self.render_template(
+ 'paste.html',
+ paste_id=paste_id,
+ paste_content=db.get(paste_id)
+ )
app = Root()
-wsgi_app = app.wsgi_app # Run with `gunicorn text:wsgi_app`
-if __name__ == "__main__":
- app.run() # Run with `python3 app.py`
diff --git a/examples/Server/pastebin/templates/index.html b/examples/pastebin/templates/index.html
similarity index 100%
rename from examples/Server/pastebin/templates/index.html
rename to examples/pastebin/templates/index.html
diff --git a/examples/Server/pastebin/templates/paste.html b/examples/pastebin/templates/paste.html
similarity index 100%
rename from examples/Server/pastebin/templates/paste.html
rename to examples/pastebin/templates/paste.html
diff --git a/examples/Server/requests/app.py b/examples/requests/app.py
similarity index 89%
rename from examples/Server/requests/app.py
rename to examples/requests/app.py
index 23e6d31..3fda164 100644
--- a/examples/Server/requests/app.py
+++ b/examples/requests/app.py
@@ -47,8 +47,8 @@ class Root(Server):
}
# Check if the request method is supported and call the handler
- if self.request in method_map:
- response = method_map[self.request]()
+ if self.scope['method'] in method_map:
+ response = method_map[self.scope['method']]()
# Ensure response is formatted correctly for WSGI
if isinstance(response, tuple):
@@ -64,6 +64,3 @@ class Root(Server):
app = Root()
-wsgi_app = app.wsgi_app # Run with `gunicorn text:wsgi_app`
-if __name__ == "__main__":
- app.run() # Run with `python3 app.py`
diff --git a/examples/Server/todolist/app.py b/examples/todolist/app.py
similarity index 94%
rename from examples/Server/todolist/app.py
rename to examples/todolist/app.py
index 6fa82b5..c32f52d 100644
--- a/examples/Server/todolist/app.py
+++ b/examples/todolist/app.py
@@ -58,10 +58,10 @@ class Root(Server):
GET /login -> Display login form
POST /login -> Authenticate user and set session
"""
- if self.request == "GET":
+ if self.scope['method'] == "GET":
return self.render_template("login.html")
- if self.request == "POST":
+ if self.scope['method'] == "POST":
username = self.body_params.get("username", [""])[0]
password = self.body_params.get("password", [""])[0]
if self.users.get(username) == password:
@@ -97,7 +97,7 @@ class Root(Server):
if not self.session.get("logged_in"):
return self.redirect("/login")
- if self.request == "POST":
+ if self.scope['method'] == "POST":
add_item(
self.body_params.get("content", [""])[0],
self.body_params.get("tags", [""])[0],
@@ -138,6 +138,3 @@ class Root(Server):
# ----------------------------------------------------------------------------
app = Root()
-wsgi_app = app.wsgi_app # Run with `gunicorn text:wsgi_app`
-if __name__ == "__main__":
- app.run() # Run with `python3 app.py`
diff --git a/examples/Server/todolist/templates/index.html b/examples/todolist/templates/index.html
similarity index 100%
rename from examples/Server/todolist/templates/index.html
rename to examples/todolist/templates/index.html
diff --git a/examples/Server/todolist/templates/login.html b/examples/todolist/templates/login.html
similarity index 100%
rename from examples/Server/todolist/templates/login.html
rename to examples/todolist/templates/login.html
diff --git a/examples/Server/todolist/templates/tag.html b/examples/todolist/templates/tag.html
similarity index 100%
rename from examples/Server/todolist/templates/tag.html
rename to examples/todolist/templates/tag.html
diff --git a/examples/Server/twutr/templates/layout.html b/examples/twutr/templates/layout.html
similarity index 100%
rename from examples/Server/twutr/templates/layout.html
rename to examples/twutr/templates/layout.html
diff --git a/examples/Server/twutr/templates/list_followers.html b/examples/twutr/templates/list_followers.html
similarity index 100%
rename from examples/Server/twutr/templates/list_followers.html
rename to examples/twutr/templates/list_followers.html
diff --git a/examples/Server/twutr/templates/list_following.html b/examples/twutr/templates/list_following.html
similarity index 100%
rename from examples/Server/twutr/templates/list_following.html
rename to examples/twutr/templates/list_following.html
diff --git a/examples/Server/twutr/templates/login.html b/examples/twutr/templates/login.html
similarity index 100%
rename from examples/Server/twutr/templates/login.html
rename to examples/twutr/templates/login.html
diff --git a/examples/Server/twutr/templates/public.html b/examples/twutr/templates/public.html
similarity index 100%
rename from examples/Server/twutr/templates/public.html
rename to examples/twutr/templates/public.html
diff --git a/examples/Server/twutr/templates/register.html b/examples/twutr/templates/register.html
similarity index 100%
rename from examples/Server/twutr/templates/register.html
rename to examples/twutr/templates/register.html
diff --git a/examples/Server/twutr/templates/timeline.html b/examples/twutr/templates/timeline.html
similarity index 100%
rename from examples/Server/twutr/templates/timeline.html
rename to examples/twutr/templates/timeline.html
diff --git a/examples/Server/twutr/templates/user.html b/examples/twutr/templates/user.html
similarity index 100%
rename from examples/Server/twutr/templates/user.html
rename to examples/twutr/templates/user.html
diff --git a/examples/Server/twutr/twutr.py b/examples/twutr/twutr.py
similarity index 98%
rename from examples/Server/twutr/twutr.py
rename to examples/twutr/twutr.py
index 4706ae1..c8ba804 100644
--- a/examples/Server/twutr/twutr.py
+++ b/examples/twutr/twutr.py
@@ -260,7 +260,7 @@ class Twutr(Server):
if not self.session.get('logged_in'):
return self.redirect('/login')
- if self.request == 'POST':
+ if self.scope['method'] == 'POST':
message = self.body_params.get('message', [''])[0]
# Convert @link syntax and escape everything else
@@ -284,7 +284,7 @@ class Twutr(Server):
if self.session.get('logged_in'):
return self.redirect('/')
- if self.request == 'POST':
+ if self.scope['method'] == 'POST':
username = escape(self.body_params.get('username', [''])[0].strip())
password = escape(self.body_params.get('password', [''])[0].strip())
@@ -306,7 +306,7 @@ class Twutr(Server):
if self.session.get('logged_in'):
return self.redirect('/')
- if self.request == 'POST':
+ if self.scope['method'] == 'POST':
username = escape(self.body_params.get('username', [''])[0].strip())
password = escape(self.body_params.get('password', [''])[0].strip())
@@ -336,6 +336,3 @@ class Twutr(Server):
app = Twutr()
-wsgi_app = app.wsgi_app # Run with `gunicorn text:wsgi_app`
-if __name__ == "__main__":
- app.run() # Run with `python3 twutr.py`
diff --git a/examples/AsyncServer/WebcamStream/app.py b/examples/websockets/app.py
similarity index 98%
rename from examples/AsyncServer/WebcamStream/app.py
rename to examples/websockets/app.py
index 5e7fa68..6aab18d 100644
--- a/examples/AsyncServer/WebcamStream/app.py
+++ b/examples/websockets/app.py
@@ -1,12 +1,12 @@
from typing import Dict, Set, Any
-from MicroPie import AsyncServer
+from MicroPie import Server
# Keep track of active users (who have "started streaming")
active_users: Set[str] = set()
streamers: Dict[str, Set[Any]] = {}
watchers: Dict[str, Set[Any]] = {}
-class MyApp(AsyncServer):
+class MyApp(Server):
async def index(self):
return self.render_template("index.html")
diff --git a/examples/AsyncServer/WebcamStream/templates/index.html b/examples/websockets/templates/index.html
similarity index 100%
rename from examples/AsyncServer/WebcamStream/templates/index.html
rename to examples/websockets/templates/index.html
diff --git a/examples/AsyncServer/WebcamStream/templates/stream.html b/examples/websockets/templates/stream.html
similarity index 100%
rename from examples/AsyncServer/WebcamStream/templates/stream.html
rename to examples/websockets/templates/stream.html
diff --git a/examples/AsyncServer/WebcamStream/templates/watch.html b/examples/websockets/templates/watch.html
similarity index 100%
rename from examples/AsyncServer/WebcamStream/templates/watch.html
rename to examples/websockets/templates/watch.html
diff --git a/examples/Server/wsgi_streaming/chatroom.py b/examples/wsgi_streaming/chatroom.py
similarity index 100%
rename from examples/Server/wsgi_streaming/chatroom.py
rename to examples/wsgi_streaming/chatroom.py
diff --git a/examples/Server/wsgi_streaming/livestream.py b/examples/wsgi_streaming/livestream.py
similarity index 100%
rename from examples/Server/wsgi_streaming/livestream.py
rename to examples/wsgi_streaming/livestream.py
diff --git a/examples/Server/wsgi_streaming/templates/index.html b/examples/wsgi_streaming/templates/index.html
similarity index 100%
rename from examples/Server/wsgi_streaming/templates/index.html
rename to examples/wsgi_streaming/templates/index.html
diff --git a/examples/Server/wsgi_streaming/templates/stream.html b/examples/wsgi_streaming/templates/stream.html
similarity index 100%
rename from examples/Server/wsgi_streaming/templates/stream.html
rename to examples/wsgi_streaming/templates/stream.html
diff --git a/examples/Server/wsgi_streaming/templates/watch.html b/examples/wsgi_streaming/templates/watch.html
similarity index 100%
rename from examples/Server/wsgi_streaming/templates/watch.html
rename to examples/wsgi_streaming/templates/watch.html
diff --git a/examples/Server/wsgi_streaming/text.py b/examples/wsgi_streaming/text.py
similarity index 100%
rename from examples/Server/wsgi_streaming/text.py
rename to examples/wsgi_streaming/text.py
diff --git a/examples/Server/wsgi_streaming/video1.py b/examples/wsgi_streaming/video1.py
similarity index 100%
rename from examples/Server/wsgi_streaming/video1.py
rename to examples/wsgi_streaming/video1.py
diff --git a/examples/Server/wsgi_streaming/video2.py b/examples/wsgi_streaming/video2.py
similarity index 100%
rename from examples/Server/wsgi_streaming/video2.py
rename to examples/wsgi_streaming/video2.py