Sabtu, 22 Maret 2025

Signals in Python: How to Shock Life Into Zombie Backends

| Sabtu, 22 Maret 2025

A Ghost Story About State Management (With a Happy Ending)

Our tale begins in 2010, when JavaScript frameworks were as elegant as a Geocities page. Along came Knockout.js—the Christopher Lee of reactivity—introducing observables that tracked dependencies like a paranormal investigator. Frontend devs whispered: “It’s alive!”

But like any good horror franchise, the story evolved:

  • 2014: Angular 1.x’s dirty checking (state management via exorcism)
  • 2016: React’s “everything re-renders” approach (the zombie apocalypse strategy)
  • 2018: SolidJS arrives, wielding Signals like a chainsaw through dependency trees
  • 2023: Angular 16 embraces Signals (the prodigal son returns… with better timing)

Meanwhile, Python backends were stuck in 1999:

# Traditional "haunted house" state management
state = {"value": 0}
listeners = []

def update(value):
    state["value"] = value
    for callback in listeners:  # Manual hauntings
        try:
            callback()
        except:
            pass  # Ghosts don't crash, right?

We became the haunted house operators of software—manually rattling chains (callbacks) to spook listeners (clients). Until...

The Sรฉance That Actually Worked: reaktiv

Enter our hero—a library that channels 13 years of frontend wisdom into Python:

from reaktiv import Signal, Effect

# Modern Python spirit board
job_status = Signal("idle")  # "running", "paused", "dead"
eff = Effect(lambda: print(f"Job status: {job_status.get()}"))
eff.schedule()

job_status.set("running")  # Prints automatically ๐Ÿ‘ป

This isn’t just syntactic sugar—it’s a sรฉance with software history. Let’s build something that would make Knockout.js’s creators proud...

Raising the Dead: A Job System That Doesn’t Suck

Scenario: Users want to control long-running tasks. Your requirements:

  • Start/pause/cancel jobs ๐ŸŽฎ
  • Live progress updates ๐Ÿ“ˆ
  • No race conditions ๐ŸŽ️๐Ÿ’จ

Traditional approach? You’d need:

  • Thread locks (❌)
  • Custom pub/sub (❌)
  • A PhD in concurrency (❌)

Let’s do it the reaktiv way:

import asyncio
from reaktiv import Signal, Effect
from fastapi import FastAPI, WebSocket
import uvicorn

# === The Beating Heart ===
job_status = Signal("idle")  # "running", "paused", "dead"
job_progress = Signal(0)
user_commands = Signal({"type": None})  # Our telepathy channel

# === The Brain (Such As It Is) ===
async def job_zombie():
    """Rises from the dead when users poke it"""
    while job_progress.get() < 100 and job_status.get() != "dead":
        if job_status.get() == "paused":
            await asyncio.sleep(1)  # Pretend to work
            continue

        # Actual "work"
        await asyncio.sleep(0.1)
        job_progress.update(lambda x: x + 1)  # 100x overpriced

        # Check for divine intervention
        cmd = user_commands.get()
        if cmd["type"] == "pause":
            job_status.set("paused")
        elif cmd["type"] == "murder":
            job_status.set("dead")

# === The Nervous System ===
app = FastAPI(title="Job Circus")

@app.post("/start")
async def awaken_the_beast():
    if job_status.get() == "idle":
        job_status.set("running")
        asyncio.create_task(job_zombie())  # Unleash hell
    return {"status": job_status.get()}

@app.post("/pause")
async def performance_anxiety():
    user_commands.set({"type": "pause"})  # Whisper to the void
    return {"status": "panic-acknowledged"}

# === The Screaming ===
active_connections = set()

@app.websocket("/ws")
async def scream_into_void(websocket: WebSocket):
    """Where progress goes to die"""
    await websocket.accept()
    active_connections.add(websocket)

    async def howl():
        await websocket.send_json({
            "status": job_status.get(),
            "progress": job_progress.get(),
            "sanity": job_progress.get()  # Coincidence?
        })

    # Black magic wiring
    eff= Effect(howl)
    eff.schedule()

    try:
        while True:
            await asyncio.sleep(1)  # Eternal damnation
    finally:
        active_connections.remove(websocket)  # Sweet release

# === Pull the Lever, Kronk! ===
if __name__ == "__main__":
    config = uvicorn.Config(app, port=8000, log_level="info")
    server = uvicorn.Server(config)
    asyncio.run(server.serve())

Why This Doesn’t Suck

1. Real-Time Voodoo

Change job_status → WebSockets auto-scream → Users actually know what’s happening.

No more “90% complete” for 3 hours (unless you want to prank users).

2. User Control That Doesn’t Lie

/pause injects commands via Signals → No more “I swear I clicked stop!”

Signals handle concurrency better than my therapist handles my anxiety.

3. Observability Without the PhD

Add monitoring in 2 lines:

# Log every status change
eff = Effect(lambda: print(f"[SPY] Status: {job_status.get()}"))
eff.schedule()

4. Scales Like a Vampire Army

Handle 10k connections like it’s a slow Tuesday. No polling. No wasted CPU cycles.

How to Torture-Test This

  1. Start the service (requires Python 3.10+):
   pip install reaktiv fastapi uvicorn websockets
   python job_horror.py
  1. Start a job:
   curl -X POST http://localhost:8000/start
  1. Watch the existential dread:
   websocat ws://localhost:8000/ws

Output:

   {"status": "running", "progress": 54, "sanity": 54}
   {"status": "running", "progress": 55, "sanity": 55}
  1. Trigger mid-life crisis:
   curl -X POST http://localhost:8000/pause

The Future Is Reactive (And Slightly Unhinged)

reaktiv isn’t just another library—it’s your ticket to building backends that feel alive. No more:

  • Manual state juggling ๐Ÿคน
  • Ghost updates ๐Ÿ‘ป
  • Race conditions ๐ŸŽ️๐Ÿ’จ

These patterns can be used to build:

  • CI/CD Pipelines that pause when your tests start crying
  • ML Training dashboards where you tweak params live
  • Payment Systems that auto-pause during fraud detection

So next time you’re:

  • Writing yet another polling loop ๐Ÿ”
  • Debugging zombie threads ๐ŸงŸ
  • Explaining why “pause” didn’t pause ๐Ÿ›‘

Remember—your backend doesn’t have to be a horror movie.

Unless you want it to be. ๐Ÿ˜Ž

Get Started:

pip install reaktiv

GitHub: https://github.com/buiapp/reaktiv

Your backend’s afterlife begins today.


Related Posts

Tidak ada komentar:

Posting Komentar