Skip to content

Commit 7c30de1

Browse files
Merge branch 'main' into reset-modules
2 parents 620c047 + 69b6c60 commit 7c30de1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+4303
-884
lines changed

.cursor/rules renamed to .cursor/rules/general.mdc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
---
2+
alwaysApply: true
3+
---
4+
15
# Cursor AI Quick Reference
26

37
**Read these files before working:**
48

5-
1. [README.md](../README.md) - Project overview and setup
6-
2. [AGENTS.md](../AGENTS.md) - AI agent guidelines and code review instructions
7-
3. [CONTRIBUTING.md](../CONTRIBUTING.md) - Coding standards and workflow
9+
1. [README.md](../../README.md) - Project overview and setup
10+
2. [AGENTS.md](../../AGENTS.md) - AI agent guidelines and code review instructions
11+
3. [CONTRIBUTING.md](../../CONTRIBUTING.md) - Coding standards and workflow
812

913
## Quick Reference
1014

.github/workflows/publish.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,14 @@ jobs:
133133
python-version: "3.11"
134134
- name: Install httpx
135135
run: pip install httpx
136-
- name: Update version in pyproject.toml and citation.cff
136+
- name: Update version in citation.cff
137137
run: python .github/workflows/update_version.py ${GITHUB_REF#refs/tags/}
138138
- name: Commit and push changes
139139
run: |
140140
git config --global user.name "github-actions[bot]"
141141
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
142-
git add pyproject.toml CITATION.cff
143-
git commit -m "Update version"
142+
git add CITATION.cff
143+
git commit -m "Update citation version"
144144
git push origin HEAD:main
145145
146146
verify:

.github/workflows/update_version.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,6 @@
55

66
if __name__ == '__main__':
77
version = sys.argv[1].lstrip('v')
8-
9-
path = Path('pyproject.toml')
10-
lines = path.read_text(encoding='utf-8').splitlines()
11-
for i, line in enumerate(lines):
12-
if line.startswith('version = '):
13-
lines[i] = f'version = "{version}.dev0"'
14-
break
15-
path.write_text('\n'.join(lines) + '\n', encoding='utf-8')
16-
178
path = Path('CITATION.cff')
189
lines = path.read_text(encoding='utf-8').splitlines()
1910
for i, line in enumerate(lines):

CITATION.cff

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ authors:
88
given-names: Rodja
99
orcid: https://orcid.org/0009-0009-4735-6227
1010
title: "NiceGUI: Web-based user interfaces with Python. The nice way."
11-
version: 3.0.4
12-
date-released: "2025-10-10"
11+
version: 3.1.0
12+
date-released: "2025-10-22"
1313
url: https://github.com/zauberzeug/nicegui
1414
doi: 10.5281/zenodo.7785516
1515
identifiers:

CONTRIBUTING.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,7 @@ If you plan to implement a new element you can follow these suggestions:
241241
8. Add a documentation file in `website/documentation/content`.
242242
By calling the `@doc.demo(...)` function with an element as a parameter the docstring is used as a description.
243243
The docstrings are written in restructured-text.
244+
Make sure to use the correct syntax (like double backticks for code).
244245
Refer to the new documentation page using `@doc.intro(...)` in any documentation section `website/documentation/content/section_*.py`.
245246
9. Create a pull-request (see below).
246247

deploy.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,20 @@ def run(cmd: list[str], *, capture: bool = False) -> str:
2121
run(['fly', 'deploy', '--wait-timeout', '600', '--lease-timeout', '30s', '--build-arg', f'VERSION={version}'])
2222

2323
instances = {
24-
'yul': 2, # Montreal, Quebec (Canada)
24+
'yyz': 2, # Toronto, Ontario (Canada)
2525
'iad': 3, # Washington DC, Virginia (US)
2626
'sjc': 2, # San Jose, California (US)
2727
'lax': 2, # Los Angeles, California (US)
2828
'mia': 2, # Miami, Florida (US)
2929
'sea': 2, # Seattle, Washington (US)
3030
'fra': 3, # Frankfurt, Germany
3131
'ams': 2, # Amsterdam, Netherlands
32-
'mad': 1, # Madrid, Spain
3332
'cdg': 2, # Paris, France
3433
'lhr': 2, # London, England (UK)
35-
'otp': 1, # Bucharest, Romania
3634
'jnb': 1, # Johannesburg, South Africa
3735
'bom': 1, # Mumbai, India
3836
'nrt': 3, # Tokyo, Japan
39-
'sin': 2, # Singapore
40-
'hkg': 3, # Hong Kong
37+
'sin': 3, # Singapore
4138
'syd': 1, # Sydney, Australia
4239
'gru': 1, # Sao Paulo, Brazil
4340
}

examples/api_requests/main.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env python3
2+
import random
3+
4+
import httpx
5+
6+
from nicegui import ui
7+
8+
9+
async def show_new_quote():
10+
async with httpx.AsyncClient() as client:
11+
response = await client.get('https://zenquotes.io/api/quotes')
12+
quote = random.choice(response.json())['q']
13+
label.text = f'“{quote}”'
14+
15+
with ui.card().classes('max-w-md mx-auto mt-20 p-12 gap-12 rounded-2xl shadow-lg border border-gray-100 items-center'):
16+
label = ui.label('Click on "Next Quote" to get a quote') \
17+
.classes('text-gray-700 italic text-lg text-center font-serif')
18+
19+
ui.button('Next Quote', on_click=show_new_quote) \
20+
.classes('bg-gray-800 hover:bg-gray-900 text-sm px-4 py-2 rounded-xl shadow-md transition duration-200')
21+
22+
ui.run()

examples/threaded_nicegui/main.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env python3
2+
import threading
3+
4+
from nicegui import Event, app, ui
5+
6+
7+
class GUI:
8+
"""Encapsulates NiceGUI in a separate thread with Event-based communication.
9+
10+
This pattern is useful for any scenario where the main thread needs to remain available for other work.
11+
"""
12+
13+
def __init__(self) -> None:
14+
self.message = Event[str]() # NOTE: broadcast data to all clients currently visiting self.root
15+
16+
def start(self) -> None:
17+
"""Start the NiceGUI server in a separate thread."""
18+
started = threading.Event()
19+
app.on_startup(started.set)
20+
thread = threading.Thread(target=lambda: ui.run(self.root, reload=False), daemon=True)
21+
thread.start()
22+
if not started.wait(timeout=3.0): # NOTE: wait for the server to start
23+
raise RuntimeError('NiceGUI did not start within 3 seconds.')
24+
25+
def root(self) -> None:
26+
"""Create the UI for each new visitor."""
27+
ui.label('NiceGUI running in separate thread').classes('text-h6')
28+
message_label = ui.label('Waiting for CLI input...')
29+
self.message.subscribe(message_label.set_text)
30+
31+
32+
if __name__ == '__main__':
33+
gui = GUI()
34+
gui.start()
35+
print('Type messages to update website from the main thread (or "quit" to exit).\n')
36+
try:
37+
while (user_input := input('> ')) != 'quit':
38+
gui.message.emit(user_input)
39+
except (EOFError, KeyboardInterrupt):
40+
pass

examples/xterm/main.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env python3
2+
"""Example for connecting a `Xterm` element with a `Bash` process running in a pty.
3+
4+
WARNING: This example gives the clients full access to the server through Bash. Use with caution!
5+
"""
6+
import os
7+
import pty
8+
import signal
9+
from functools import partial
10+
11+
from nicegui import core, events, ui
12+
13+
14+
@ui.page('/')
15+
def _page():
16+
terminal = ui.xterm()
17+
18+
pty_pid, pty_fd = pty.fork() # create a new pseudo-terminal (pty) fork of the process
19+
if pty_pid == pty.CHILD:
20+
os.execv('/bin/bash', ('bash',)) # child process of the fork gets replaced with "bash"
21+
print('Terminal opened')
22+
23+
@partial(core.loop.add_reader, pty_fd)
24+
def pty_to_terminal():
25+
try:
26+
data = os.read(pty_fd, 1024)
27+
except OSError:
28+
print('Stopping reading from pty') # error reading the pty; probably bash was exited
29+
core.loop.remove_reader(pty_fd)
30+
else:
31+
terminal.write(data)
32+
33+
@terminal.on_data
34+
def terminal_to_pty(event: events.XtermDataEventArguments):
35+
try:
36+
os.write(pty_fd, event.data.encode('utf-8'))
37+
except OSError:
38+
pass # error writing to the pty; probably bash was exited
39+
40+
@ui.context.client.on_delete
41+
def kill_bash():
42+
os.close(pty_fd)
43+
os.kill(pty_pid, signal.SIGKILL)
44+
print('Terminal closed')
45+
46+
47+
ui.run()

fetch_milestone.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99

1010
parser = argparse.ArgumentParser(description='Fetch the content of a milestone from a GitHub repo.')
1111
parser.add_argument('milestone_title', help='Title of the milestone to fetch.')
12+
parser.add_argument('--check', action='store_true', help='Check if the description mentions all issues.')
1213
args = parser.parse_args()
1314
milestone_title: str = args.milestone_title
15+
check: bool = args.check
1416

1517
page = 0
1618
while True:
@@ -23,6 +25,7 @@
2325
matching = [m for m in milestones if m['title'] == milestone_title]
2426
if matching:
2527
milestone_number = matching[0]['number']
28+
milestone_description = matching[0]['description']
2629
break
2730

2831

@@ -32,13 +35,23 @@ def link(number: int) -> str:
3235
return escape_mask.format('', f'https://github.com/zauberzeug/nicegui/issues/{number}', f'#{number}')
3336

3437

35-
issues = httpx.get(f'{BASE_URL}/issues?milestone={milestone_number}&state=all', timeout=5).json()
38+
issues = []
39+
page = 0
40+
while True:
41+
page += 1
42+
response = httpx.get(f'{BASE_URL}/issues?milestone={milestone_number}&state=all&page={page}', timeout=5)
43+
page_issues = response.json()
44+
if not page_issues:
45+
break
46+
issues.extend(page_issues)
47+
3648
notes: dict[str, list[str]] = {
3749
'New features and enhancements': [],
3850
'Bugfixes': [],
3951
'Documentation': [],
4052
'Testing': [],
4153
'Dependencies': [],
54+
'Infrastructure': [],
4255
'Others': [],
4356
}
4457
for issue in issues:
@@ -63,6 +76,8 @@ def link(number: int) -> str:
6376
notes['Testing'].append(note)
6477
elif 'dependencies' in labels:
6578
notes['Dependencies'].append(note)
79+
elif 'infrastructure' in labels:
80+
notes['Infrastructure'].append(note)
6681
else:
6782
notes['Others'].append(note)
6883

@@ -74,3 +89,8 @@ def link(number: int) -> str:
7489
for line in lines:
7590
print(f'- {line}')
7691
print()
92+
93+
if check:
94+
for issue in issues:
95+
if f'#{issue["number"]}' not in milestone_description:
96+
print(f'Issue {link(issue["number"])} is not mentioned in the milestone description')

0 commit comments

Comments
 (0)