Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions nicegui-a-browser-frontend-for-your-python-code/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# NiceGUI: A Browser Frontend for Your Python Code

Sample scripts that build small user interfaces with [NiceGUI](https://nicegui.io/). Each NiceGUI file is a complete app. `csv_to_markdown.py` is a plain CLI helper used by the UI example.

## Setup

These examples need Python 3.10 or newer. Create a virtual environment and install the dependencies:

```console
$ python -m venv venv
$ source venv/bin/activate
$ python -m pip install -r requirements.txt
```

On Windows, activate the environment with `venv\Scripts\activate`.

`requirements.txt` installs NiceGUI 3.17.0 with the Plotly extra, along with pinned versions of Plotly, pandas, and [pywebview](https://pywebview.flowrl.com/). `password_generator_native.py` uses pywebview to open a desktop window. On Linux, pywebview also needs a system WebKit or Qt backend. The [pywebview installation guide](https://pywebview.flowrl.com/guide/installation.html) lists the packages for each platform.

## Examples

| Script | What it shows |
| --- | --- |
| `element_catalog.py` | Common inputs (text, number, select, checkbox, radio, slider, switch) and a summary label |
| `estimate_card.py` | A card with quantity, unit price, optional tax, and a calculated total |
| `password_generator.py` | A password generator in the browser |
| `password_generator_native.py` | The same password generator in a native desktop window |
| `csv_to_markdown.py` | CLI that converts a CSV file to a Markdown table |
| `csv_to_markdown_ui.py` | Browser UI for the converter, with progress and `run.io_bound` |
| `request_chart.py` | An ECharts bar chart and a table of request counts |
| `plotly_chart.py` | A Plotly bar chart from a pandas DataFrame |

Run a NiceGUI example from this folder:

```console
$ python password_generator.py
```

NiceGUI prints a local URL, usually `http://127.0.0.1:8080`. Open that address in a browser. Stop the app with Ctrl+C.

The native example opens its own window:

```console
$ python password_generator_native.py
```

The CLI converter works without starting a server:

```console
$ python csv_to_markdown.py people.csv
```
68 changes: 68 additions & 0 deletions nicegui-a-browser-frontend-for-your-python-code/csv_to_markdown.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import argparse
import csv
from io import StringIO
from pathlib import Path


def csv_to_markdown(
text: str,
*,
delimiter: str = ",",
header: bool = True,
) -> str:
raw = text.strip()
if not raw:
raise ValueError("CSV text is empty")

rows = list(csv.reader(StringIO(raw), delimiter=delimiter))
if not rows:
raise ValueError("CSV text has no rows")
if any(len(row) != len(rows[0]) for row in rows):
raise ValueError("CSV rows do not all have the same number of columns")

if header:
headings = [cell.strip() or " " for cell in rows[0]]
body = rows[1:]
else:
headings = [f"Col {index}" for index in range(1, len(rows[0]) + 1)]
body = rows

lines = [
"| " + " | ".join(headings) + " |",
"| " + " | ".join("---" for _ in headings) + " |",
]
for row in body:
lines.append("| " + " | ".join(cell.strip() for cell in row) + " |")
return "\n".join(lines) + "\n"


def main() -> None:
parser = argparse.ArgumentParser(
description="Convert a CSV file to a Markdown table."
)
parser.add_argument("csv_file", type=Path, help="path to a CSV file")
parser.add_argument(
"-d",
"--delimiter",
default=",",
help="field delimiter (default: comma)",
)
parser.add_argument(
"--no-header",
action="store_true",
help="treat the first row as data",
)
args = parser.parse_args()
text = args.csv_file.read_text(encoding="utf-8")
print(
csv_to_markdown(
text,
delimiter=args.delimiter,
header=not args.no_header,
),
end="",
)


if __name__ == "__main__":
main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from nicegui import run, ui

from csv_to_markdown import csv_to_markdown

SAMPLE = "name,role\nAda,Engineer\nGrace,Scientist\n"

source = ui.textarea("CSV", value=SAMPLE).classes("w-full").props("rows=8")
delimiter = ui.select(
{",": "Comma", ";": "Semicolon", "\t": "Tab"},
value=",",
label="Delimiter",
)
header = ui.checkbox("First row is the header", value=True)
status = ui.label("Paste CSV, then click Convert.")
progress = ui.linear_progress(show_value=False, size="8px").props(
"indeterminate"
)
progress.set_visibility(False)
markdown = ui.textarea("Markdown").classes("w-full").props("rows=8")


async def convert() -> None:
raw = source.value or ""
convert_button.disable()
progress.set_visibility(True)
status.set_text("Converting")
try:
result = await run.io_bound(
csv_to_markdown,
raw,
delimiter=delimiter.value,
header=header.value,
)
except ValueError as exc:
markdown.set_value("")
status.set_text(str(exc))
ui.notify(str(exc), type="negative")
return
finally:
progress.set_visibility(False)
convert_button.enable()

markdown.set_value(result)
status.set_text("Conversion complete.")
ui.notify("Markdown table ready", type="positive")


convert_button = ui.button("Convert", on_click=convert)

ui.run()
28 changes: 28 additions & 0 deletions nicegui-a-browser-frontend-for-your-python-code/element_catalog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from nicegui import ui

name = ui.input("Project", value="notes")
notes = ui.textarea("Notes", value="Ship the export")
copies = ui.number("Copies", value=2, min=1, max=10)
kind = ui.select(["Draft", "Final"], value="Draft", label="Kind")
urgent = ui.checkbox("Urgent")
ui.label("Extension")
extension = ui.radio(["txt", "csv"], value="txt")
ui.label("Priority")
priority = ui.slider(min=0, max=10, value=3)
archive = ui.switch("Archive after")
summary = ui.label("No choices yet")


def show() -> None:
summary.set_text(
f"{name.value}: {kind.value}.{extension.value}, "
f"copies={copies.value}, urgent={urgent.value}, "
f"priority={priority.value}, archive={archive.value}"
)
if notes.value:
ui.notify(notes.value)


ui.button("Show choices", on_click=show)

ui.run()
28 changes: 28 additions & 0 deletions nicegui-a-browser-frontend-for-your-python-code/estimate_card.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from nicegui import ui

with ui.row().classes("w-full items-center gap-4 p-4"):
ui.icon("request_quote", size="md").props("color=primary")
ui.label("Estimate").classes("text-h6 text-weight-bold")

with ui.card().classes("w-80"):
quantity = ui.number("Quantity", value=2, min=1)
unit_price = ui.number("Unit price", value=40.0, min=0, format="%.2f")
taxed = ui.checkbox("Add 20% tax")
total = ui.label("Total: —").classes("text-subtitle1")

def calculate() -> None:
qty = quantity.value
price = unit_price.value
if qty is None or price is None or qty < 1 or price < 0:
total.set_text("Total: —")
ui.notify("Enter a quantity and a unit price", type="warning")
return

amount = float(qty) * float(price)
if taxed.value:
amount *= 1.2
total.set_text(f"Total: {amount:.2f}")

ui.button("Calculate", on_click=calculate).props("color=primary")

ui.run()
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import secrets
import string

from nicegui import ui

length = ui.number("Length", value=16, min=8, max=64)
digits = ui.checkbox("Include digits", value=True)
symbols = ui.checkbox("Include symbols")
password = ui.input("Password").props("readonly")
status = ui.label("Click Generate for a new password.")


def generate() -> None:
size = length.value
if size is None or size != int(size) or size < 8 or size > 64:
password.set_value("")
status.set_text("Choose a whole length from 8 to 64.")
ui.notify("Length must be a whole number from 8 to 64", type="warning")
return

alphabet = string.ascii_letters
if digits.value:
alphabet += string.digits
if symbols.value:
alphabet += "!@#$%^&*()-_=+"

size = int(size)
result = "".join(secrets.choice(alphabet) for _ in range(size))
password.set_value(result)
status.set_text(f"Generated a {size}-character password.")


ui.button("Generate", on_click=generate)

ui.run()
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import secrets
import string

from nicegui import ui

length = ui.number("Length", value=16, min=8, max=64)
digits = ui.checkbox("Include digits", value=True)
symbols = ui.checkbox("Include symbols")
password = ui.input("Password").props("readonly")
status = ui.label("Click Generate for a new password.")


def generate() -> None:
size = length.value
if size is None or size != int(size) or size < 8 or size > 64:
password.set_value("")
status.set_text("Choose a whole length from 8 to 64.")
ui.notify("Length must be a whole number from 8 to 64", type="warning")
return

alphabet = string.ascii_letters
if digits.value:
alphabet += string.digits
if symbols.value:
alphabet += "!@#$%^&*()-_=+"

size = int(size)
result = "".join(secrets.choice(alphabet) for _ in range(size))
password.set_value(result)
status.set_text(f"Generated a {size}-character password.")


ui.button("Generate", on_click=generate)

ui.run(native=True)
3 changes: 3 additions & 0 deletions nicegui-a-browser-frontend-for-your-python-code/people.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name,role
Ada,Engineer
Grace,Scientist
26 changes: 26 additions & 0 deletions nicegui-a-browser-frontend-for-your-python-code/plotly_chart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import pandas as pd
import plotly.express as px
from nicegui import ui

traffic = pd.DataFrame(
{
"Endpoint": [
"/login",
"/api/items",
"/api/search",
"/checkout",
"/health",
"/admin",
],
"Requests": [1280, 2540, 1875, 940, 4120, 210],
}
)
fig = px.bar(
traffic,
x="Endpoint",
y="Requests",
title="Requests by Endpoint",
)
ui.plotly(fig).classes("w-full")

ui.run()
28 changes: 28 additions & 0 deletions nicegui-a-browser-frontend-for-your-python-code/request_chart.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from nicegui import ui

traffic = [
{"endpoint": "/login", "requests": 1280},
{"endpoint": "/api/items", "requests": 2540},
{"endpoint": "/api/search", "requests": 1875},
{"endpoint": "/checkout", "requests": 940},
{"endpoint": "/health", "requests": 4120},
{"endpoint": "/admin", "requests": 210},
]

ui.echart(
{
"title": {"text": "Requests by Endpoint"},
"xAxis": {
"type": "category",
"data": [row["endpoint"] for row in traffic],
},
"yAxis": {"type": "value"},
"series": [
{"type": "bar", "data": [row["requests"] for row in traffic]}
],
}
).classes("w-full")

ui.table(rows=traffic)

ui.run()
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
nicegui[plotly]==3.17.0
plotly==6.9.0
pandas==3.0.5
pywebview==6.2.1
Loading