Metadata-Version: 2.4
Name: varnapy
Version: 0.1.1
Summary: VarnaPy — reactive Python web UI framework, build beautiful apps without JavaScript
Project-URL: Homepage, https://github.com/Minato3000/VarnaPy
Project-URL: Repository, https://github.com/Minato3000/VarnaPy
Project-URL: Documentation, https://minato3000.github.io/VarnaPy
Project-URL: Bug Tracker, https://github.com/Minato3000/VarnaPy/issues
Project-URL: Changelog, https://github.com/Minato3000/VarnaPy/blob/main/CHANGELOG.md
Author-email: VarnaPy Contributors <raghuram@iitmpravartak.net>
License: MIT License
        
        Copyright (c) 2025 FluxUI Contributors
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: components,dashboard,fastapi,frontend,gradio,reactive,streamlit,ui,web,websocket
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: FastAPI
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: anyio>=4.3
Requires-Dist: fastapi>=0.111
Requires-Dist: jinja2>=3.1
Requires-Dist: python-multipart>=0.0.9
Requires-Dist: typing-extensions>=4.9; python_version < '3.11'
Requires-Dist: uvicorn[standard]>=0.29
Requires-Dist: websockets>=12.0
Provides-Extra: all
Requires-Dist: pandas>=1.5; extra == 'all'
Requires-Dist: plotly>=5.18; extra == 'all'
Requires-Dist: polars>=0.20; extra == 'all'
Requires-Dist: requests>=2.31; extra == 'all'
Provides-Extra: auth
Requires-Dist: passlib[bcrypt]>=1.7; extra == 'auth'
Requires-Dist: python-jose[cryptography]>=3.3; extra == 'auth'
Provides-Extra: charts
Requires-Dist: plotly>=5.18; extra == 'charts'
Provides-Extra: dev
Requires-Dist: hatch>=1.12; extra == 'dev'
Requires-Dist: httpx>=0.27; extra == 'dev'
Requires-Dist: mypy>=1.10; extra == 'dev'
Requires-Dist: pre-commit>=3.7; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff>=0.4; extra == 'dev'
Requires-Dist: watchfiles>=0.21; extra == 'dev'
Provides-Extra: pandas
Requires-Dist: pandas>=1.5; extra == 'pandas'
Provides-Extra: polars
Requires-Dist: polars>=0.20; extra == 'polars'
Provides-Extra: share
Requires-Dist: requests>=2.31; extra == 'share'
Description-Content-Type: text/markdown

# VarnaPy

**Reactive Python web UI framework — build beautiful apps without JavaScript.**

[![PyPI](https://img.shields.io/pypi/v/varnapy)](https://pypi.org/project/varnapy/)
[![Python](https://img.shields.io/pypi/pyversions/varnapy)](https://pypi.org/project/varnapy/)
[![CI](https://github.com/Minato3000/FluxApp/actions/workflows/ci.yml/badge.svg)](https://github.com/Minato3000/FluxApp/actions)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

---

## Why VarnaPy?

| Feature | VarnaPy | Streamlit¹ | Gradio¹ |
|---------|--------|-----------|--------|
| Targeted re-renders (no full page rerun) | ✅ | ❌ | ❌ |
| Multi-page SPA routing | ✅ | partial | ❌ |
| Component override system | ✅ | ❌ | ❌ |
| Third-party plugin ecosystem | ✅ | ✅ | partial |
| Dark mode | ✅ | ✅ | ❌ |
| File upload | ✅ | ✅ | ✅ |
| Charts (Plotly) | ✅ | ✅ | partial |
| DataGrid with sort + pagination | ✅ | partial | ❌ |
| WebSocket protocol (sub-6KB JS) | ✅ | ❌ | ❌ |

> ¹ Streamlit and Gradio are trademarks of their respective owners. Feature assessments based on publicly available documentation as of June 2026 and may change with newer releases.

---

## Installation

```bash
pip install varnapy
```

Optional extras:

```bash
pip install "varnapy[charts]"   # Plotly Chart component
pip install "varnapy[share]"    # Public tunnel (share=True)
pip install "varnapy[all]"      # Everything
```

---

## Quickstart

```python
import varnapy as ui

app = ui.App("My App")

@app.page("/")
def home(session):
    count = ui.state(0)

    with ui.Column(gap="md"):
        ui.Heading("Hello from VarnaPy", level=1)
        ui.Button("Click me", on_click=lambda: count.set(count.value + 1))
        ui.Text(lambda: f"Clicked {count.value} times")

app.run()
```

```bash
varnapy run app.py
# → http://127.0.0.1:8000
```

---

## Core Concepts

### Reactive State

```python
# Simple value
count = ui.state(0)
count.set(10)
count.update(lambda v: v + 1)
count.value  # → 11

# Derived / computed
full_name = ui.computed(lambda: f"{first.value} {last.value}")

# Side effects
ui.effect(lambda: save_to_db(query.value))

# Mutable containers
items = ui.list_state(["a", "b"])
items.append("c")

config = ui.dict_state({"debug": False})
config["debug"] = True
```

Any `ReactiveVar` read inside a component's render function automatically subscribes that component. When the value changes, **only that component** re-renders — never the full page.

### Component Library

**Layout**
```python
ui.Row(gap="md")
ui.Column(gap="lg")
ui.Grid(cols=3)
ui.Card(title="Stats", description="Overview")
ui.Tabs(["Overview", "Details", "Settings"])
ui.Modal(open=show_modal, on_close=lambda: show_modal.set(False))
ui.Sidebar(open=sidebar_open)
```

**Input**
```python
ui.Button("Save", on_click=save, variant="primary")
ui.TextInput(label="Name", value=name, on_change=lambda v: name.set(v))
ui.NumberInput(label="Amount", value=amt, min=0, max=1000)
ui.Select(label="Status", options=["Draft", "Published"], value=status)
ui.Checkbox(label="Agree", checked=agreed)
ui.Switch(label="Dark mode", checked=dark_mode, on_change=toggle_theme)
ui.Slider(label="Volume", value=vol, min=0, max=100)
ui.FileUpload("Drop files here", multiple=True, on_change=handle_files)
```

**Display**
```python
ui.Text("Body text")
ui.Heading("Page Title", level=1)
ui.Metric(label="Revenue", value="$12,400", delta="+8.2%")
ui.Badge("Active", variant="success")
ui.Progress(75.0, label="Upload progress")
ui.Table(data, columns=["name", "email", "status"])
ui.Chart(plotly_figure, height="400px")
ui.Code("print('hello')", language="python")
```

**Feedback & Navigation**
```python
ui.Alert("Saved!", variant="success")
ui.Spinner(size="md")
ui.NavMenu(brand="My App")
ui.NavItem("Home", href="/", active=True)
ui.Pagination(page=current_page, total_pages=10)
```

**Advanced**
```python
ui.DataGrid(data, columns=[{"key": "name", "label": "Name", "sortable": True}], page_size=20)
ui.CustomComponent()  # base class for custom components with client-side JS
```

### Multi-Page Routing

```python
@app.page("/")
def home(session):
    ui.Heading("Home")
    ui.Button("Go to profile", on_click=lambda: session.navigate("/profile/42"))

@app.page("/profile/{user_id}")
def profile(session, user_id: str):
    ui.Heading(f"Profile: {user_id}")
```

Navigation is SPA-style — no full page reload.

### Dark Mode

```python
dark = ui.state(False)

async def toggle(v: bool) -> None:
    dark.set(v)
    await session.set_theme("dark" if v else "light")

ui.Switch(label="Dark mode", checked=dark, on_change=toggle)
```

### Component Overrides

Replace any built-in component app-wide or per-page:

```python
from varnapy.components._base import Component

class MyButton(Component):
    def render(self) -> str:
        return f'<button {self._attrs("my-btn")} data-flux-event="click">{self._label}</button>'

# App-wide
ui.override("Button", MyButton)

# Single page only
@app.page("/special", overrides={"Button": MyButton})
def special(session): ...
```

### Third-Party Plugins

Publish component libraries to PyPI with a single entry-point:

```toml
# your plugin's pyproject.toml
[project.entry-points."varnapy.components"]
my_plugin = "my_package:register"
```

```python
# my_package/__init__.py
def register(registry):
    registry.add("MyWidget", MyWidget)
```

VarnaPy discovers installed plugins automatically on startup.

---

## CLI

```bash
varnapy run app.py                   # dev server, hot-reload, opens browser
varnapy run app.py --port 3000       # custom port
varnapy run app.py --no-open         # don't open browser
varnapy run app.py --no-reload       # disable hot-reload
varnapy run app.py --prod            # production mode (no dev overlay)
varnapy run app.py --share           # public URL via localtunnel

varnapy new my_app                   # scaffold new project
varnapy export-docker app.py         # generate Dockerfile + docker-compose.yml
```

---

## Testing

```python
from varnapy.testing import FluxTestClient

def test_counter():
    app = ui.App("test")

    @app.page("/")
    def home(session):
        count = ui.state(0)
        ui.Button("Inc", on_click=lambda: count.set(count.value + 1))
        ui.Text(lambda: str(count.value))

    with FluxTestClient(app) as client:
        client.connect("/")
        assert "0" in client.html()
        client.click("Inc")
        assert "1" in client.html()
```

---

## Production Deployment

### Docker

```bash
varnapy export-docker app.py --output ./deploy
docker compose -f deploy/docker-compose.yml up
```

### Manual

```bash
pip install "varnapy[all]" gunicorn
gunicorn app:app._fastapi -w 1 -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000
```

Note: Use 1 worker per process (sessions are in-process). For horizontal scaling, add Redis-backed session sharing (roadmap).

---

## Examples

| Example | Description |
|---------|-------------|
| [`hello_world.py`](examples/hello_world.py) | Counter in 15 lines |
| [`multi_page.py`](examples/multi_page.py) | SPA routing, DataGrid, Chart, FileUpload |

---

## License

MIT — see [LICENSE](LICENSE).

Built by [Minato3000](https://github.com/Minato3000).
