Skip to main content

inguitive

A pure Python web framework combining intuitive syntax with HTMX for partial page reloads and Tailwind CSS for styling.

Unlike traditional request-response frameworks, inguitive provides reactive state management where components automatically re-render when state changes, eliminating the need for manual DOM manipulation or JavaScript. It is designed for Python developers who want to build interactive web applications using only Python, without sacrificing the dynamic feel of modern SPAs.

Features

  • Reactive State Management: Components automatically re-render when state changes
  • HTMX Integration: Out-of-band swaps for seamless partial page updates
  • Component-Based: Composable UI components with clean Python syntax
  • Dynamic Attributes: All component attributes can be static strings or callables
  • Trigger Arguments: Pass data from components to handlers via trigger_args and get_trigger_args()
  • Type Safe: Full type hints throughout the codebase
  • Tailwind CSS: First-class support for utility-first styling

Quick Start

Installation

pip install inguitive

Basic Example

from inguitive import Div, Button, Label, State, create_app
from inguitive.css import BUTTON_PRIMARY_CSS

# Create FastAPI app
# Optionally, you can assign a title and/or favicon to the app (which will appear
# in the browser tab). Fallback to Inguitive's title and favicon when omitted.
app = create_app(title="Counter App", favicon="/path/to/favicon.svg")

# Create reactive state
counter_state = State(0, "counter_state")

# Define a trigger function
@app.trigger_handler
def increment():
    counter_state.set(counter_state.get() + 1)

# Define a component
def Counter():
    return Div(
        Label(text=lambda: f"Count: {counter_state.get()}", id="counter-label", listen_to="counter_state"),
        Button("+1", trigger="increment", css=BUTTON_PRIMARY_CSS),
    )

# Define a route function
# Each page can optionally have its own title and/or favicon. 
# Fallback to app-level title/favicon if defined, else Inguitive's title and favicon
# are used by default.
@app.page("/", title="Counter Page", favicon="/path/to/page_favicon.svg")
def index():
    return Counter()

Trigger Arguments

Pass data from a component to its handler using trigger_args on the component and get_trigger_args() in the handler:

from inguitive import Button, Div, State, Text, create_app, get_trigger_args

app = create_app()
selected_state = State("none", "selected_state")

@app.trigger_handler
def select_item():
    item_id = get_trigger_args().get("id")
    selected_state.set(item_id)

@app.page("/")
def index():
    return Div(
        Button("Select A", trigger="select_item", trigger_args={"id": "a"}),
        Button("Select B", trigger="select_item", trigger_args={"id": "b"}),
        Text(lambda: f"Selected: {selected_state.get()}", listen_to="selected_state"),
    )

trigger_args are passed as URL query parameters; get_trigger_args() returns them as a dict[str, str] inside the handler.

Component Reference

inguitive provides a comprehensive set of components organized by category. All components support dynamic attributes via callables and can listen to state changes for automatic re-rendering.

Base Components

Component Description Key Parameters Example
Component Base class for all components id, css, listen_to Custom component base
TemplateComponent Render Jinja2 templates template, context vars Custom HTML with templating

Layout Components

Component Description Key Parameters Example
Div Container div element *children, id, css Div(Button("Click"), css="flex gap-2")
Text Paragraph/text element text, id, css Text("Hello", css="text-xl")
Label Form label element text, for_, id, css Label("Name:", for_="name")

Form Components

Component Description Key Parameters Example
Form Form container *children, action, method Form(Input(...), Button(...))
Input Text input field type, value, placeholder, listen_to Input(id="email", type="email")
Textarea Multi-line text input value, placeholder, rows Textarea(id="bio", rows=5)
Select Dropdown select options, value, listen_to Select(id="country", options=[...])
Checkbox Checkbox input checked, id, listen_to Checkbox(id="agree", checked=True)
Radio Radio button input value, checked, name Radio(id="male", name="gender")
Button Clickable button *children, trigger, css Button("Click", trigger="action")

Navigation Components

Component Description Key Parameters Example
Link Semantic navigation link *children, href, css Link("Home", href="/")

Data Display Components

Component Description Key Parameters Example
DataTable Tabular data display data, columns, css DataTable(data=[{"name": "A"}])
Icon SVG icon component svg, css Icon("<svg ...>...</svg>", css="w-6 h-6")

Common Parameters (All Components)

Parameter Type Description
id str | None HTML id attribute. Required for state listening and OOB updates
css str | Callable[[], str] | dict | None Tailwind CSS classes. For DataTable, can be a dict with keys: table, header, row, cell
listen_to str | list[str] | None State name(s) to listen for changes. Triggers re-render when state updates
trigger str | None Trigger name for HTMX POST actions (Button, Input, etc.)
trigger_args dict[str, str] | None Query parameters to pass with trigger

Navigation & Actions

Use Link for traditional navigation (SEO, bookmarking, new-tab support) and trigger for partial page updates:

Link(href="...") trigger="..."
Renders <a href="..."> HTMX POST
URL changes
Open in new tab
from inguitive import Link, Button

# Traditional navigation
Link("Home", href="/")
Link("Documentation", href="/docs", css="text-blue-500")

# Partial updates
Button("Save", trigger="save_form")
Button("Like", trigger="like_post", trigger_args={"id": "123"})

Project Structure

.
├── src/
│   └── inguitive/
│       ├── __init__.py      # Public API
│       ├── components.py    # Component classes
│       ├── state.py         # Reactive state
│       ├── htmx.py           # HTMX helpers
│       ├── fastapi.py       # FastAPI integration
│       └── svg.py           # SVG icon definitions
├── examples/
│   ├── counter_app.py        # Per-session counter with theme toggle
│   ├── todo_app.py           # CRUD with filtering and real-time count
│   ├── chat_app.py           # Real-time chat
│   ├── navigation_demo.py    # Link vs trigger patterns
│   ├── registration_form.py  # Form handling
│   └── data_table_app.py     # DataTable with sorting and filtering
├── tests/
│   └── test_*.py           # Test files
├── pyproject.toml           # Build configuration
└── README.md

Session Backends

inguitive uses session-scoped registries to isolate user state. Choose a backend based on your deployment needs:

Backend Use When Persistence Multi-Worker
MemoryBackend Development, single worker ❌ No (lost on restart) ❌ No
RedisBackend Production, multiple workers ✅ Yes ✅ Yes

MemoryBackend (default) stores sessions in RAM - perfect for development. RedisBackend stores sessions in Redis for production deployments with multiple workers or persistent sessions.

from inguitive import create_app, MemoryBackend, RedisBackend

# Development: In-memory sessions (default, no config needed)
app = create_app()

# Or explicitly:
app = create_app(session_backend=MemoryBackend())

# Production: Redis-backed sessions for scaling
app = create_app(
    session_backend=RedisBackend(
        redis_url="redis://localhost:6379",
        ttl_seconds=3600  # Session timeout: 1 hour
    )
)

Requires pip install redis for RedisBackend.

Session Lifetime and Expiry

inguitive sessions are created automatically on first request and persist across page reloads. Each session has isolated component, state, and data registries.

Session Creation: A new session is created with a unique ID when a user first visits your application. The session ID is stored in a cookie.

Session Persistence: Sessions persist across page reloads and browser navigation within the same domain. The session cookie maintains the session ID, allowing the framework to restore the user's state.

Session Expiry:

  • MemoryBackend: Sessions expire after ttl_seconds (default: 3600 = 1 hour) of inactivity. Expired sessions are automatically cleaned up every N requests (configurable via session_cleanup_interval).
  • RedisBackend: Sessions are stored in Redis with a TTL. Redis automatically expires keys after the configured ttl_seconds, providing automatic cleanup.

Differences Between Backends:

Aspect MemoryBackend RedisBackend
Persistence Lost on process restart Survives process restarts
Multi-worker Not supported (shared memory) Supported (Redis as shared store)
Cleanup Manual/Periodic via cleanup_expired() Automatic via Redis TTL
Use Case Development, testing Production, scaling

Page Reload Behavior: Session state is preserved across page reloads. Components listening to state will re-render with the current state values when the page loads.

Production Deployment

Before deploying your inguitive app to production, configure these security settings:

from inguitive import create_app, RedisBackend

app = create_app(
    title="My App",
    favicon="/path/to/favicon.ico",
    session_backend=RedisBackend(redis_url="redis://localhost:6379"),
    session_cookie_secure=True,      # Cookies only over HTTPS
    session_cookie_httponly=True,    # Prevent JavaScript access (default)
    session_cookie_max_age=86400,    # 24-hour session timeout
)

Checklist:

  • ✅ Use RedisBackend (not MemoryBackend) for persistence across workers
  • ✅ Set session_cookie_secure=True when using HTTPS
  • ✅ Verify session_cookie_httponly=True (enabled by default)
  • ✅ Deploy with HTTPS (required for secure cookies)

Running the Demo

# From the repository root
uvicorn examples.counter_app:app --reload

# Then open http://localhost:8000

License

MIT License - see LICENSE for details.

Contact

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

inguitive-0.4.0.tar.gz (52.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

inguitive-0.4.0-py3-none-any.whl (35.3 kB view details)

Uploaded Python 3

File details

Details for the file inguitive-0.4.0.tar.gz.

File metadata

  • Download URL: inguitive-0.4.0.tar.gz
  • Upload date:
  • Size: 52.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for inguitive-0.4.0.tar.gz
Algorithm Hash digest
SHA256 5d9734acfa11e3669243a14e054d932b3a4fe8fa2dd89d9275b6b258adc105cf
MD5 05bfce5b081cb3d9c71e6b083707fa81
BLAKE2b-256 4d2f295bc5fdd3b75dd3106acc5f93a1f89b4a4831fe528611d7aed70e1d2580

See more details on using hashes here.

File details

Details for the file inguitive-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: inguitive-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 35.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for inguitive-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 a2620a8c2fe5f33765d746fbe797b2adc4fd104b0bc81468268ceba2135bc42b
MD5 0cc00b6d5a86f8dbfdc6669826f0fbd6
BLAKE2b-256 7e983464a8a583f1908d6a80cbc8f290e5ac1743895dcc1219b096fb5fcb9a55

See more details on using hashes here.

Release history Release notifications | RSS feed

1.0.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.1

2 files

This release

0.4.0 This release

2 files

0.3.0

2 files

0.2.0

2 files

0.1.3

2 files

0.1.2

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page