patx/micropie
working on fixing request state leaks, added Request class and now each handler can access the current request state with self.request
Commit 239c4a4 · patx · 2025-01-30T22:27:43-05:00
Comments
No comments yet.
Diff
diff --git a/MicroPie.py b/MicroPie.py
index 867cf7c..f36353f 100644
--- a/MicroPie.py
+++ b/MicroPie.py
@@ -1,35 +1,3 @@
-"""
-MicroPie: A simple Python ultra-micro web framework with ASGI
-support. https://patx.github.io/micropie
-
-Copyright 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.
-"""
import inspect
import mimetypes
import os
@@ -37,6 +5,7 @@ import time
from typing import Optional, Dict, Any, Union, Tuple, List
from urllib.parse import parse_qs
import uuid
+import contextvars
try:
from jinja2 import Environment, FileSystemLoader
@@ -45,6 +14,18 @@ try:
except ImportError:
JINJA_INSTALLED = False
+# Create a context variable to store the current request
+current_request = contextvars.ContextVar('current_request')
+
+class Request:
+ def __init__(self, scope):
+ self.scope = scope
+ self.method = scope["method"]
+ self.path_params = []
+ self.query_params = {}
+ self.body_params = {}
+ self.session = {}
+ self.files = {}
class Server:
SESSION_TIMEOUT: int = 8 * 3600 # 8 hours
@@ -53,131 +34,136 @@ class Server:
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.files: Dict[str, Any] = {}
+ self.sessions: Dict[str, Any] = {}
+
+ @property
+ def request(self) -> Request:
+ return current_request.get()
async def __call__(self, scope, receive, send):
await self.asgi_app(scope, receive, send)
async def asgi_app(self, scope: Dict[str, Any], receive: Any, send: Any) -> None:
"""ASGI application entrypoint for both HTTP and WebSockets."""
-
if scope["type"] == "http":
- self.scope = scope
- method = scope["method"]
- path = scope["path"].lstrip("/")
- 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)
-
- raw_query = scope.get("query_string", b"")
- self.query_params = parse_qs(raw_query.decode("utf-8", "ignore"))
-
- headers_dict = {
- k.decode("latin-1").lower(): v.decode("latin-1")
- for k, v in scope.get("headers", [])
- }
- cookies = self._parse_cookies(headers_dict.get("cookie", ""))
-
- session_id = 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:
- self.session = {}
-
- self.body_params = {}
- self.files = {}
- if method in ("POST", "PUT", "PATCH"):
- body_data = bytearray()
- while True:
- msg = await receive()
- if msg["type"] == "http.request":
- body_data += msg.get("body", b"")
- if not msg.get("more_body"):
- break
- content_type = headers_dict.get("content-type", "")
- if "multipart/form-data" in content_type:
- self.parse_multipart(bytes(body_data), content_type)
- else:
- body_str = body_data.decode("utf-8", "ignore")
- self.body_params = parse_qs(body_str)
-
- 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:
- await self._send_response(
- send,
- status_code=400,
- body=f"400 Bad Request: Missing required parameter '{param.name}'",
- )
- return
-
- if handler_function == getattr(self, "index", None) and not func_args and path:
- await self._send_response(send, status_code=404, body="404 Not Found")
- return
+ request = Request(scope)
+ # Set the current request in the context variable
+ token = current_request.set(request)
try:
- if inspect.iscoroutinefunction(handler_function):
- result = await handler_function(*func_args)
- else:
- result = handler_function(*func_args)
- except Exception as e:
- print(f"Error processing request: {e}")
- await self._send_response(
- send, status_code=500, body="500 Internal Server Error"
- )
- return
-
- status_code = 200
- response_body = result
- extra_headers: List[Tuple[str, str]] = []
+ method = scope["method"]
+ path = scope["path"].lstrip("/")
+ path_parts = path.split("/") if path else []
+ func_name = path_parts[0] if path_parts else "index"
+ request.path_params = path_parts[1:] if len(path_parts) > 1 else []
+
+ handler_function = getattr(self, func_name, None)
+ if not handler_function:
+ request.path_params = path_parts
+ handler_function = getattr(self, "index", None)
+
+ raw_query = scope.get("query_string", b"")
+ request.query_params = parse_qs(raw_query.decode("utf-8", "ignore"))
+
+ headers_dict = {
+ k.decode("latin-1").lower(): v.decode("latin-1")
+ for k, v in scope.get("headers", [])
+ }
+ cookies = self._parse_cookies(headers_dict.get("cookie", ""))
- if isinstance(result, tuple):
- if len(result) == 2:
- status_code, response_body = result
- elif len(result) == 3:
- status_code, response_body, extra_headers = result
+ session_id = cookies.get("session_id")
+ if session_id and session_id in self.sessions:
+ request.session = self.sessions[session_id]
+ request.session["last_access"] = time.time()
else:
+ request.session = {}
+
+ request.body_params = {}
+ request.files = {}
+ if method in ("POST", "PUT", "PATCH"):
+ body_data = bytearray()
+ while True:
+ msg = await receive()
+ if msg["type"] == "http.request":
+ body_data += msg.get("body", b"")
+ if not msg.get("more_body"):
+ break
+ content_type = headers_dict.get("content-type", "")
+ if "multipart/form-data" in content_type:
+ self.parse_multipart(bytes(body_data), content_type, request)
+ else:
+ body_str = body_data.decode("utf-8", "ignore")
+ request.body_params = parse_qs(body_str)
+
+ sig = inspect.signature(handler_function)
+ func_args = []
+ for param in sig.parameters.values():
+ if request.path_params:
+ func_args.append(request.path_params.pop(0))
+ elif param.name in request.query_params:
+ func_args.append(request.query_params[param.name][0])
+ elif param.name in request.body_params:
+ func_args.append(request.body_params[param.name][0])
+ elif param.name in request.files:
+ func_args.append(request.files[param.name])
+ elif param.name in request.session:
+ func_args.append(request.session[param.name])
+ elif param.default is not param.empty:
+ func_args.append(param.default)
+ else:
+ await self._send_response(
+ send,
+ status_code=400,
+ body=f"400 Bad Request: Missing required parameter '{param.name}'",
+ )
+ return
+
+ if handler_function == getattr(self, "index", None) and not func_args and path:
+ await self._send_response(send, status_code=404, body="404 Not Found")
+ return
+
+ try:
+ if inspect.iscoroutinefunction(handler_function):
+ result = await handler_function(*func_args)
+ else:
+ result = handler_function(*func_args)
+ except Exception as e:
+ print(f"Error processing request: {e}")
await self._send_response(
- send, status_code=500,
- body="500 Internal Server Error: Invalid response tuple"
+ send, status_code=500, body="500 Internal Server Error"
)
return
- if self.session:
- session_id = cookies.get("session_id", str(uuid.uuid4()))
- self.sessions[session_id] = self.session # Store session only if used
- extra_headers.append(("Set-Cookie", f"session_id={session_id}; Path=/; HttpOnly; SameSite=Strict"))
-
- await self._send_response(
- send,
- status_code=status_code,
- body=response_body,
- extra_headers=extra_headers
- )
+ status_code = 200
+ response_body = result
+ extra_headers: List[Tuple[str, str]] = []
+
+ if isinstance(result, tuple):
+ if len(result) == 2:
+ status_code, response_body = result
+ elif len(result) == 3:
+ status_code, response_body, extra_headers = result
+ else:
+ await self._send_response(
+ send, status_code=500,
+ body="500 Internal Server Error: Invalid response tuple"
+ )
+ return
+
+ if request.session:
+ session_id = cookies.get("session_id", str(uuid.uuid4()))
+ self.sessions[session_id] = request.session # Store session only if used
+ extra_headers.append(("Set-Cookie", f"session_id={session_id}; Path=/; HttpOnly; SameSite=Strict"))
+
+ await self._send_response(
+ send,
+ status_code=status_code,
+ body=response_body,
+ extra_headers=extra_headers
+ )
+ finally:
+ # Reset the context variable to avoid leaking request state
+ current_request.reset(token)
else:
pass
@@ -191,7 +177,7 @@ class Server:
cookies[k] = v
return cookies
- def parse_multipart(self, body: bytes, content_type: str) -> None:
+ def parse_multipart(self, body: bytes, content_type: str, request: Request) -> None:
boundary = None
parts = content_type.split(";")
for part in parts:
@@ -241,17 +227,17 @@ class Server:
if filename:
file_content_type = header_dict.get("content-type", "application/octet-stream")
- self.files[name] = {
+ request.files[name] = {
"filename": filename,
"content_type": file_content_type,
"data": content
}
elif name:
value = content.decode("utf-8", "ignore")
- if name in self.body_params:
- self.body_params[name].append(value)
+ if name in request.body_params:
+ request.body_params[name].append(value)
else:
- self.body_params[name] = [value]
+ request.body_params[name] = [value]
async def _send_response(
self,
@@ -369,4 +355,3 @@ class Server:
return self.env.get_template(name).render(kwargs)
return await asyncio.get_event_loop().run_in_executor(None, render_sync)
-
diff --git a/README.md b/README.md
index dbc0bee..244fb1c 100644
--- a/README.md
+++ b/README.md
@@ -84,11 +84,11 @@ class MyApp(Server):
return f"Form submitted by: {username}"
async def submit_catch_all(self):
- username = self.body_params.get("username", ["Anonymous"])[0]
+ username = self.request.body_params.get("username", ["Anonymous"])[0]
return f"Submitted by: {username}"
```
-By default, MicroPie's route handlers can accept any request method, it's up to you how to handle any incoming requests! You can check the request method in the handler with the`scope["method"]`.
+By default, MicroPie's route handlers can accept any request method, it's up to you how to handle any incoming requests! You can check the request method (and an number of other things specific to the current request state) in the handler with`self.request.method`.
### **3. Real-Time Communication with Socket.IO**
Because of its designed simplicity, MicroPie does not handle WebSockets out of the box. While the underlying ASGI interface can theoretically handle WebSocket connections, MicroPie’s routing and request-handling logic is designed primarily for HTTP. While MicroPie does not natively support WebSockets, you can easily integrate dedicated Websockets libraries like **Socket.IO** alongside Uvicorn to handle real-time, bidirectional communication. Check out [examples/socketio](https://github.com/patx/micropie/tree/main/examples/socketio) to see this in action.
@@ -140,10 +140,10 @@ Built-in session handling simplifies state management:
class MyApp(Server):
async def index(self):
if "visits" not in self.session:
- self.session["visits"] = 1
+ self.request.session["visits"] = 1
else:
- self.session["visits"] += 1
- return f"You have visited {self.session['visits']} times."
+ self.request.session["visits"] += 1
+ return f"You have visited {self.request.session['visits']} times."
```
### **8. Deployment**
@@ -187,29 +187,6 @@ MicroPie allows you to take full advantage of these benefits while maintaining s
| **Async Support** | Yes (ASGI) | No (Quart) | No | No | Limited | Yes (ASGI) |
| **Built-in Server** | No | No | Yes | Yes | Yes | No |
-### **Performance vs Other ASGI Frameworks**
-| Connections | Framework | Requests/sec | Latency (ms) | Transfer/sec (KB) |
-|-------------|------------|--------------|--------------|--------------------|
-| 100 | FastAPI | 1895.29 | 52.67 | 257.54 |
-| | MicroPie | 2272.76 | 43.93 | 362.18 |
-| | Quart | 1500.13 | 66.63 | 212.68 |
-| | Starlette | 2305.50 | 43.30 | 326.88 |
-| 200 | FastAPI | 2015.90 | 99.78 | 273.94 |
-| | MicroPie | 2516.01 | 79.31 | 400.92 |
-| | Quart | 1574.15 | 126.63 | 223.16 |
-| | Starlette | 2658.45 | 75.10 | 376.86 |
-| 1000 | FastAPI | 2129.72 | 463.63 | 289.44 |
-| | MicroPie | 2589.64 | 381.86 | 412.79 |
-| | Quart | 1557.83 | 628.80 | 221.01 |
-| | Starlette | 2887.07 | 342.99 | 409.37 |
-
-
-Starlette performs best, maintaining the highest throughput and low latency due to its heavily optimized architecture. MicroPie also excels, especially at high concurrency,
-benefiting from lightweight processing. FastAPI offers stable performance but suffers increased latency under load, likely due to request validation overhead. Quart
-performs the worst, with high latency and low throughput, likely due to its Flask compatibility, making it less suited for high-concurrency workloads.
-
-*Tests were performed on a Star Labs StarLite Mk IV with `uvicorn` using 4 workers. Benchmarked with `wrk` with 4 threads for 30s. This a minimal baseline benchmark, and should be taken with a grain of salt.*
-
## **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/file_uploads/app.py b/examples/file_uploads/app.py
index fa7bb10..e6d24ca 100644
--- a/examples/file_uploads/app.py
+++ b/examples/file_uploads/app.py
@@ -13,7 +13,7 @@ class Root(Server):
await loop.run_in_executor(None, self._write_file, upload_path, data)
except IOError as e:
return 500, f"Failed to save file: {str(e)}"
- return 200, f"File uploaded successfully as '{os.path.basename(upload_path)}'. <a href='/'>Upload another</a>"
+ return f"File uploaded successfully as '{os.path.basename(upload_path)}'. <a href='/'>Upload another</a>"
def _write_file(self, upload_path, data):
"""
diff --git a/examples/headers/app.py b/examples/headers/app.py
index 34ccae6..38e3ce9 100644
--- a/examples/headers/app.py
+++ b/examples/headers/app.py
@@ -11,7 +11,7 @@ class Root(Server):
("Strict-Transport-Security", "max-age=31536000; includeSubDomains"),
("Content-Security-Policy", "default-src 'self'")
]
- return 200, "hello world", headers
+ return 200, "<b>hello world</b>", headers
diff --git a/examples/pastebin/app.py b/examples/pastebin/app.py
index 866ac4d..b7f51e3 100644
--- a/examples/pastebin/app.py
+++ b/examples/pastebin/app.py
@@ -15,8 +15,8 @@ class Root(Server):
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]
+ if self.request.method == "POST":
+ paste_content = self.request.body_params.get('paste_content', [''])[0]
pid = str(uuid4())
db.set(pid, escape(paste_content))
db.save()
diff --git a/examples/requests/app.py b/examples/requests/app.py
index 3fda164..651cd32 100644
--- a/examples/requests/app.py
+++ b/examples/requests/app.py
@@ -47,7 +47,7 @@ class Root(Server):
}
# Check if the request method is supported and call the handler
- if self.scope['method'] in method_map:
+ if self.request.method in method_map:
response = method_map[self.scope['method']]()
# Ensure response is formatted correctly for WSGI
diff --git a/examples/streaming/text.py b/examples/streaming/text.py
index 096aad8..a748598 100644
--- a/examples/streaming/text.py
+++ b/examples/streaming/text.py
@@ -1,4 +1,5 @@
import time
+import asyncio
from MicroPie import Server
class Root(Server):
@@ -7,12 +8,12 @@ class Root(Server):
# Normal, immediate response (non-streaming)
return "Hello from index!"
- def slow_stream(self):
- # Streaming response using a generator
- def generator():
+ async def slow_stream(self):
+ # Streaming response using an async generator
+ async def generator():
for i in range(1, 6):
- yield f"Chunk {i}\n"
- time.sleep(1) # simulate slow processing or data generation
+ yield f"Chunk {i} "
+ await asyncio.sleep(1)
return generator()
diff --git a/examples/streaming/video.py b/examples/streaming/video.py
index fe4601d..e72fe81 100644
--- a/examples/streaming/video.py
+++ b/examples/streaming/video.py
@@ -19,9 +19,10 @@ class Root(Server):
'''
async def stream(self):
+ # Access the request headers using the self.request property
headers = {
k.decode('latin-1').lower(): v.decode('latin-1')
- for k, v in self.scope.get('headers', [])
+ for k, v in self.request.scope.get('headers', [])
}
range_header = headers.get('range')
file_size = os.path.getsize(VIDEO_PATH)
diff --git a/examples/twutr/twutr.py b/examples/twutr/twutr.py
index 62b1d76..4955df1 100644
--- a/examples/twutr/twutr.py
+++ b/examples/twutr/twutr.py
@@ -150,26 +150,26 @@ class Twutr(Server):
async def index(self):
"""Shows the user's timeline (the messages of people they follow, including their own)."""
- if not self.session.get('logged_in'):
+ if not self.request.session.get('logged_in'):
return self.redirect('/public')
- user_id = self.session.get('user_id')
+ user_id = self.request.session.get('user_id')
all_messages = get_all_messages_for_user_and_following(user_id)
all_messages = sort_messages_by_timestamp(all_messages, timestamp_index=2)
- return await self.render_template('timeline.html', messages=all_messages, session=self.session)
+ return await self.render_template('timeline.html', messages=all_messages, session=self.request.session)
async def public(self):
"""Displays the latest messages of all users with usernames."""
all_messages = get_all_messages_from_all_users()
all_messages = sort_messages_by_timestamp(all_messages, timestamp_index=2)
- return await self.render_template('public.html', messages=all_messages, session=self.session)
+ return await self.render_template('public.html', messages=all_messages, session=self.request.session)
async def user(self, username):
"""Displays a specific user's messages."""
- logged_in = self.session.get('logged_in')
- current_user = self.session.get('user_id')
+ logged_in = self.request.session.get('logged_in')
+ current_user = self.request.session.get('user_id')
username = escape(username)
# Determine if current_user is following, is the same user, or is not logged in
@@ -193,7 +193,7 @@ class Twutr(Server):
'user.html',
messages=messages,
username=username,
- session=self.session,
+ session=self.request.session,
following=following,
followers=followers,
following_count=following_count
@@ -203,11 +203,11 @@ class Twutr(Server):
def follow(self, username):
"""Follow another user."""
- if not self.session.get('logged_in'):
+ if not self.request.session.get('logged_in'):
return self.redirect('/login')
username = escape(username)
- current_user = self.session.get('user_id')
+ current_user = self.request.session.get('user_id')
if username == current_user:
return "You cannot follow yourself"
@@ -217,10 +217,10 @@ class Twutr(Server):
def unfollow(self, username):
"""Unfollow another user."""
- if not self.session.get('logged_in'):
+ if not self.request.session.get('logged_in'):
return self.redirect('/login')
- current_user = self.session.get('user_id')
+ current_user = self.request.session.get('user_id')
update_follow_relationship(current_user, escape(username), follow=False)
return self.redirect(f'/user/{username}')
@@ -237,7 +237,7 @@ class Twutr(Server):
'list_followers.html',
username=username,
followers=followers,
- session=self.session
+ session=self.request.session
)
async def list_following(self, username):
@@ -252,68 +252,68 @@ class Twutr(Server):
'list_following.html',
username=username,
following=following,
- session=self.session
+ session=self.request.session
)
def add_message(self):
"""Registers a new message for the logged-in user with custom link and mention handling."""
- if not self.session.get('logged_in'):
+ if not self.request.session.get('logged_in'):
return self.redirect('/login')
- if self.scope['method'] == 'POST':
- message = self.body_params.get('message', [''])[0]
+ if self.request.method == 'POST':
+ message = self.request.body_params.get('message', [''])[0]
# Convert @link syntax and escape everything else
sanitized_message = convert_custom_syntax(message)
# Prevent empty message submissions
if not sanitized_message.strip():
- return self.render_template('timeline.html', error="Message cannot be empty", session=self.session)
+ return self.render_template('timeline.html', error="Message cannot be empty", session=self.request.session)
time_stamp = str(datetime.utcnow().strftime('%m/%d/%Y %I:%M %p'))
message_tuple = (sanitized_message, time_stamp)
- user_data = get_user_data(self.session.get('user_id'))
+ user_data = get_user_data(self.request.session.get('user_id'))
user_data['messages'].append(message_tuple)
- save_user_data(self.session.get('user_id'), user_data)
+ save_user_data(self.request.session.get('user_id'), user_data)
return self.redirect('/')
async def login(self):
"""Logs the user in."""
- if self.session.get('logged_in'):
+ if self.request.session.get('logged_in'):
return self.redirect('/')
- if self.scope['method'] == 'POST':
- username = escape(self.body_params.get('username', [''])[0].strip())
- password = escape(self.body_params.get('password', [''])[0].strip())
+ if self.request.method == 'POST':
+ username = escape(self.request.body_params.get('username', [''])[0].strip())
+ password = escape(self.request.body_params.get('password', [''])[0].strip())
if not username or not password:
- return await self.render_template('login.html', error="Fields cannot be empty", session=self.session)
+ return await self.render_template('login.html', error="Fields cannot be empty", session=self.request.session)
user = get_user_data(username)
if not user or user['password'] != password:
- return await self.render_template('login.html', error="Invalid credentials", session=self.session)
+ return await self.render_template('login.html', error="Invalid credentials", session=self.request.session)
- self.session['user_id'] = username
- self.session['logged_in'] = True
+ self.request.session['user_id'] = username
+ self.request.session['logged_in'] = True
return self.redirect('/')
- return await self.render_template('login.html', session=self.session)
+ return await self.render_template('login.html', session=self.request.session)
async def register(self):
"""Registers a new user."""
- if self.session.get('logged_in'):
+ if self.request.session.get('logged_in'):
return self.redirect('/')
- if self.scope['method'] == 'POST':
- username = escape(self.body_params.get('username', [''])[0].strip())
- password = escape(self.body_params.get('password', [''])[0].strip())
+ if self.request.method == 'POST':
+ username = escape(self.request.body_params.get('username', [''])[0].strip())
+ password = escape(self.request.body_params.get('password', [''])[0].strip())
if not username or not password:
- return await self.render_template('login.html', error="Fields cannot be empty", session=self.session)
+ return await self.render_template('login.html', error="Fields cannot be empty", session=self.request.session)
if db.get(username):
- return await self.render_template('register.html', session=self.session, error="Username already taken.")
+ return await self.render_template('register.html', session=self.request.session, error="Username already taken.")
db.set(str(username), {
'username': username,
@@ -325,12 +325,12 @@ class Twutr(Server):
db.save()
return self.redirect('/login')
- return await self.render_template('register.html', session=self.session)
+ return await self.render_template('register.html', session=self.request.session)
def logout(self):
"""Logs the user out."""
- if self.session.get('logged_in'):
- self.session.clear()
+ if self.request.session.get('logged_in'):
+ self.request.session.clear()
return self.redirect('/public')