Skip to content

Releases: VxidDev/Nebula

Nebula - 3.4.1

Choose a tag to compare

@VxidDev VxidDev released this 08 Apr 16:24

What's Changed

Full Changelog: 3.3.0...3.4.1

Nebula - 3.3.0

Choose a tag to compare

@VxidDev VxidDev released this 03 Apr 16:16

Nebula 3.3.0 - Release Notes

What’s New

⚠️ Error Dispatcher & Injection System (Major)

Nebula introduces a flexible injection system for error handlers along with a fully redesigned error dispatch pipeline.

Error handlers can now declare exactly what they need, and Nebula will inject it automatically.


Error Handler Injection

Error handlers now support explicit parameter injection.

Supported parameters:

  • request - current request object
  • scope - raw ASGI scope
  • receive - ASGI receive callable
  • send - ASGI send callable
  • code - HTTP status code

Example

@app.error_handler(404)
async def not_found(request, code):
    return f"<h1>{code} - {request.path} not found</h1>"

Only declared parameters are passed, nothing extra.


⚠️ Important Behavior

  • Injection is name-based, not positional
  • Only explicitly declared parameters are provided
  • Works for both sync and async handlers
  • No runtime introspection, everything is cached at registration

Error Dispatcher

Nebula’s internal _dispatch_error system has been completely redesigned.

Key Improvements

  • Zero overhead at runtime

    • No inspect calls during request handling
  • 🧠 Precomputed handler metadata

    • Accepted parameters cached
    • Async/sync status cached
  • 🔁 Unified execution path

    • Same logic for all error handlers

How It Works

  1. Error occurs (404 / 405 / 500 or HTTPException)
  2. Matching handler is resolved
  3. Required parameters are injected
  4. Handler is executed (sync or async)
  5. Return value is normalized into a Response

Strict Signature Validation

Error handlers are now validated at registration time.

Allowed parameters:

{"scope", "receive", "send", "request", "code"}

Example (invalid)

@app.error_handler(404)
def handler(foo):  # ❌ not allowed
    ...

➡️ Raises:

ExtraArgumentsDetected

⚠️ Behavior Note

  • Invalid signatures fail early (at startup)
  • Prevents runtime bugs and undefined behavior
  • Ensures consistent handler contracts

Automatic Response Handling

Error handlers can return any supported type:

  • Response → used directly

  • dict / listJSONResponse

  • str → auto-detected:

    • HTML → HTMLResponse
    • URL → RedirectResponse
    • otherwise → PlainTextResponse

Example

@app.error_handler(500)
def internal_error(code):
    return {"error": "Internal Server Error", "status": code}

Request Context Access

Nebula now provides a global request proxy:

from nebula import request

@app.error_handler(500)
def handler():
    return f"Error on {request.path}"

⚠️ Important Behavior

  • Powered by ContextVar (async-safe)
  • Only available inside request lifecycle
  • Access outside request raises:
RuntimeError

Default Error Handlers

Built-in handlers (404, 405, 500) now:

  • Use the same injection system
  • Follow the same response normalization rules
  • Are fully replaceable via @app.error_handler(...)

Internal Improvements

  • Added _error_handler_params cache
  • Added _error_handler_is_async cache
  • Removed runtime inspect usage from hot path
  • Improved separation of dispatch and execution
  • Optimized error handling performance

⚠️ Breaking Changes

1. Restricted Error Handler Signatures

Old:

def handler(foo, bar): ...

New:

def handler(request, code): ...

Only supported parameters are allowed.

Nebula - 3.2.0

Choose a tag to compare

@VxidDev VxidDev released this 01 Apr 16:37

Nebula 3.2.0 - Release Notes

What’s New

⚠️ Middleware System Update (Major)

Nebula now supports composable middleware at two levels:

  • Global ASGI Middleware - wraps the entire application
  • Route-level Middleware - applied per route or RouteGroup

This enables fine-grained control over request handling while maintaining full ASGI compatibility.


Middleware

Middleware in Nebula follows the ASGI pattern:

class BaseMiddleware:
    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        await self.app(scope, receive, send)

Middleware is applied using the Middleware wrapper:

class Middleware:
    def __init__(self, middleware_cls: type, **options):
        self.middleware_cls = middleware_cls
        self.options = options

    def build(self, app):
        return self.middleware_cls(app, **self.options)

🌍 Global Middleware

Global middleware wraps the entire application:

app = Nebula(
    middlewares=[
        Middleware(LoggingMiddleware)
    ]
)

🧩 Route & Group Middleware

Middleware can now be applied per route or route group:

api = app.group(
    "/api",
    middlewares=[
        Middleware(APILoggerMiddleware)
    ]
)

Route-level middleware can also be passed directly:

@api.get("/greet/{name}", route_middlewares=[...])
async def greet(name: str):
    return {"greeting": f"Hi, {name}!"}

⚠️ Important Behavior

  • Middleware is applied in stacked order
  • Global middleware runs before group middleware
  • Route-specific middleware runs last
  • Middleware is composed per request, not statically wrapped at the app level

RouteGroup

RouteGroup is now a first-class abstraction for organizing routes and applying scoped middleware.

It allows you to:

  • Group routes under a shared prefix
  • Attach middleware to a logical section of your app
  • Keep code modular and maintainable

Example

api = app.group(
    "/api",
    middlewares=[
        Middleware(APILoggerMiddleware)
    ]
)

@api.get("/greet/{name}")
async def greet(name: str):
    return {"greeting": f"Hi, {name}!"}

⚠️ Behavior Note

  • RouteGroup does not wrap the ASGI app directly

  • Instead, its middleware is composed into the request pipeline

  • This ensures:

    • correct routing behavior
    • full compatibility with ASGI
    • no unintended global side effects

Middleware Execution Order

For a request, middleware is executed in this order:

  1. Global middleware (outermost)
  2. Group middleware
  3. Route middleware
  4. Route handler

This creates a predictable and composable execution pipeline.

Nebula - 3.1.4

Choose a tag to compare

@VxidDev VxidDev released this 31 Mar 16:37

Nebula 3.1.4 - Release Notes

What’s New

⚠️ Behavior change: Automatic response wrapping has been updated.

Added RouteGroup, enabling cleaner and more organized route definitions, and laying the foundation for future middleware support and route isolation.


RouteGroup

RouteGroup is a utility that helps simplify and organize route definitions. It also introduces the groundwork for future middleware isolation between the main Nebula application and grouped routes.

Examples

api: RouteGroup = app.group("/api")

@api.get("/weather")
async def weather():
    return {"temperature": 27}

⚠️ Middleware isolation is not yet implemented. For now, RouteGroup acts as a structural wrapper.


New Behavior

Previously, you could explicitly pass a response class:

@app.get("/", return_class=HTMLResponse)
async def root() -> None:
     return "<h1>Welcome to Nebula!</h1>"

Now, Nebula also inspects the route’s return type annotation. If a valid class that inherits from Response is provided, and the user has not explicitly passed return_class, the return type annotation will be used automatically.


Examples

# returns HTMLResponse

@app.get("/", return_class=HTMLResponse)
async def root() -> None:
    return "<h1>Welcome to Nebula!</h1>"

# also returns HTMLResponse
@app.get("/a")
async def something() -> HTMLResponse:
    return "<h1>Welcome to Nebula!</h1>"

# returns PlainTextResponse
@app.get("/b", return_class=PlainTextResponse)
async def something_again() -> HTMLResponse:
    return "<h1>Welcome to Nebula!</h1>"

Nebula - 3.1.2

Choose a tag to compare

@VxidDev VxidDev released this 29 Mar 15:36

Nebula 3.1.2 - Release Notes

What’s New

⚠️ Behavior change: Automatic response wrapping has been introduced.

Route return values are now automatically converted into the appropriate response type, removing the need to explicitly specify return_class in most cases.


New Behavior

Previously, you had to define a response class explicitly:

@app.get("/", return_class=HTMLResponse)
async def root() -> None:
    return "<h1>Welcome to Nebula!</h1>"

Now, Nebula automatically detects the response type:

@app.get("/")
async def root() -> None:
    return "<h1>Welcome to Nebula!</h1>"

Auto-Detection Rules

Nebula will now automatically wrap return values using the following rules:

  • dict or listJSONResponse

  • str:

    • Starts with <HTMLResponse
    • Otherwise → PlainTextResponse
  • Other types → converted to str and wrapped in PlainTextResponse


Examples

✅ JSON Response (automatic)

@app.get("/json")
async def json_route():
    return {"message": "Hello, world!"}

✅ HTML Response (automatic)

@app.get("/html")
async def html_route():
    return "<h1>Hello, world!</h1>"

✅ Plain Text Response (automatic)

@app.get("/text")
async def text_route():
    return "Hello, world!"

❌ Old behavior (no longer required)

@app.get("/", return_class=HTMLResponse)
async def root() -> None:
    return "<h1>Welcome to Nebula!</h1>"

Migration Guide

If your routes previously relied on return_class, you can safely:

  • ❌ Remove return_class from your route definitions
  • ✅ Keep your return values unchanged

Before

@app.get("/", return_class=HTMLResponse)
async def root() -> None:
    return "<h1>Welcome to Nebula!</h1>"

After

@app.get("/")
async def root() -> None:
    return "<h1>Welcome to Nebula!</h1>"

Notes

  • This change simplifies route definitions and reduces boilerplate.
  • Response behavior is now inferred automatically based on return type.
  • You can still explicitly return a Response instance to bypass auto-detection.

Nebula - 3.1.1

Choose a tag to compare

@VxidDev VxidDev released this 29 Mar 15:08

Nebula 3.1.1 - Release Notes

What’s New

⚠️ Behavior change: The request object is no longer always injected automatically into route handlers.

Request is now only injected when one of the following conditions is met:

a) The parameter is named request
b) The parameter is type-annotated with Request (or a compatible class)

If neither condition is met, the request can still be accessed via nebula.get_request().


Examples

❌ No request injection

from nebula.response import HTMLResponse

@app.get("/", return_class=HTMLResponse)
async def root() -> HTMLResponse:
    return "<h1>Welcome to Nebula!</h1>"

❌ No request injection (other parameters)

@app.get("/{something}", return_class=HTMLResponse)
async def something(something) -> HTMLResponse:
    return f"<h1>Welcome to {something}!</h1>"

✅ Request injected via type annotation

from nebula.request import Request
from nebula.response import HTMLResponse

@app.get("/route", return_class=HTMLResponse)
async def route(request: Request) -> HTMLResponse:
    return f"<h1>You're currently on: http://{app.host}:{app.port}{request.path}</h1>"

✅ Request injected regardless of parameter name

@app.get("/route2", return_class=HTMLResponse)
async def route2(req: Request) -> HTMLResponse:
    return f"<h1>You're currently on: http://{app.host}:{app.port}{req.path}</h1>"

✅ Access request via get_request()

from nebula import get_request
from nebula.response import HTMLResponse

@app.get("/route3", return_class=HTMLResponse)
async def route3() -> HTMLResponse:
    return f"<h1>You're currently on: http://{app.host}:{app.port}{get_request().path}</h1>"

Migration Guide

If your existing routes relied on implicit request injection, update them by:

  • Adding request: Request to your route handler, or
  • Using nebula.get_request() inside your function

Notes

  • The parameter name does not matter if it is annotated with Request.
  • This change makes request handling more explicit and predictable.

Nebula - 3.1.0

Choose a tag to compare

@VxidDev VxidDev released this 28 Mar 18:59

Release notes for Nebula 3.1.0

Whats new?

  • Overall optimization of Nebula
  • Added Nebula.get , Nebula.post, Nebula.put, Nebula.delete
  • Added return_class to Nebula.route and methods above
  • Added Nebula.run (uses run_dev)
  • Added render_template (sync) and render_template_async
  • Added render_template_string (sync) and render_template_async

Example Code

from nebula import Nebula
from nebula.response import HTMLResponse

app = Nebula(__file__, "localhost", 8000)

@app.get("/", return_class=HTMLResponse)
async def root(request) -> None:
    return "<h1>Welcome to Nebula!</h1>"

if __name__ == "__main__":
    app.run()

Summary

This update introduces optimizations and some new functions, while keeping the old code compatible.

Nebula - 3.0.0

Choose a tag to compare

@VxidDev VxidDev released this 27 Mar 17:22

Nebula 3.0.0 Release Notes

Nebula 3.0.0 is a major update that transitions the framework from WSGI to ASGI, bringing native async support and a more modern architecture.

Highlights

  • Full migration to ASGI

  • Nebula no longer depends on eventlet or werkzeug

  • Built for modern async Python

  • Native async support

  • Route handlers now support async def

  • Better performance and scalability

  • New development runner

  • Introduced run_dev() for quick local development

  • No need for external WSGI servers like Gunicorn

  • Simplified API

  • Cleaner routing

  • More intuitive request handling

Breaking Changes

  • WSGI removed

  • app.wsgi_app is no longer available

  • Gunicorn + eventlet setup is no longer supported

  • Routes are now async-first

  • Handlers can use async def

  • request object is now passed to route functions

  • Routing signature simplified

  • endpoint argument is no longer required in most cases

Migration Guide

Before (WSGI)

# tiny.py
from nebula import Nebula
from nebula.utils import htmlify

app = Nebula(__file__, "localhost", 8000)

@app.route("/", endpoint="main")
def root() -> None:
    return htmlify("<h1>Welcome to Nebula!</h1>")

main = app.wsgi_app  # gunicorn -w 1 -k eventlet tiny:main

After (ASGI)

from nebula import Nebula, run_dev
from nebula.utils import htmlify

app = Nebula(__file__, "localhost", 8000)

@app.route("/")
async def root(request) -> None:
    return htmlify("<h1>Welcome to Nebula!</h1>")

run_dev(app)

What This Means

  • More aligned with modern Python frameworks
  • Ready for high-performance async workloads
  • Easier to use for modern web development

Final Notes

This is a breaking release, but it sets the foundation for future features and performance improvements.

If you were using Nebula before, migrating is straightforward and worth it for the async benefits.

Nebula - 2.2.0

Choose a tag to compare

@VxidDev VxidDev released this 21 Mar 10:06

Nebula Session Management Feature - Release Notes

New feature just arrived! Now you can easily manage user sessions and authentication in your Nebula applications. This update introduces a secure, cookie-based session management system that integrates seamlessly with both standard HTTP routes and Socket.IO event handlers.

Key features include:

  • Cookie-Based Sessions: Securely store session data on the client using HMAC-signed cookies.
  • User Authentication: A simple API to log users in, log them out, and check their authentication status.
  • Route Protection: A decorator to easily protect routes and require user login.
  • WebSocket Integration: Authenticate users for your real-time WebSocket connections.
  • Flexible User Model: Integrates with your existing user model via a simple mixin.

How to Use

1. Enable Sessions

First, enable session management by calling app.setup_sessions() with a secret key. This is crucial for signing the session cookies securely.

from nebula import Nebula

app = Nebula(__file__, "0.0.0.0", 5000)
app.init_all()

# Enable sessions with a secret key
app.setup_sessions(secret_key="a-super-secret-key-that-you-should-change")

2. Create a User Model

Your user class should inherit from UserMixin to be compatible with Nebula's authentication system. This mixin provides default properties like is_authenticated.

from nebula import UserMixin

class User(UserMixin):
    def __init__(self, user_id, username):
        self.id = user_id
        self.username = username

3. Implement a User Loader

Nebula needs a way to load a user object from the ID stored in the session. Create a function for this and decorate it with @app.user_loader.

# A dummy user database
USERS = {"1": User("1", "alice"), "2": User("2", "bob")}

@app.user_loader
def load_user(user_id: str):
    return USERS.get(user_id)

4. Log Users In and Out

Use the login_user and logout_user functions to manage user authentication state.

from nebula import login_user, logout_user, current_user
from werkzeug.utils import redirect

@app.route("/login", methods=["POST"])
def login():
    # In a real app, you'd validate a username and password
    user = USERS.get("1") # Example: logging in user "alice"
    if user:
        login_user(user)
    return redirect("/dashboard")

@app.route("/logout")
def logout():
    logout_user()
    return redirect("/login")

5. Protect Routes

Use the @login_required() decorator to protect routes that require authentication. Unauthenticated users will be redirected to the specified login page.

from nebula import login_required

@app.route("/dashboard")
@login_required(redirect_to="/login")
def dashboard():
    return f"Welcome, {current_user.username}!"

6. Authenticate WebSocket Connections

You can also authenticate users for your Socket.IO connections. Use app.get_session_from_environ() in your on_connect handler to access the session and verify the user.

@app.on_connect()
def on_connect(sid, environ):
    session = app.get_session_from_environ(environ)
    user_id = session.get("_user_id")

    if not user_id:
        return False  # Reject connection

    user = load_user(user_id)
    if not user:
        return False # Reject connection

    print(f"{user.username} connected to WebSocket")
    # You can now associate the user with the connection (sid)

New Example: Authenticated Chat

To see this feature in action, check out the new chat_auth.py example in the examples/ directory. It demonstrates how to build a real-time chat application with session-based authentication.

Nebula - 2.1.1

Choose a tag to compare

@VxidDev VxidDev released this 07 Mar 17:54

This release features an useful addition of HTTPS support.

Usage

app.run(ssl_context=("localhost+2.pem", "localhost+2-key.pem")) # example for localhost 

Support is still not flawless, but will get polished in future.