Fix ui.upload unregisters wrong url on delete (#6236) - #6256
Conversation
evnchn
left a comment
There was a problem hiding this comment.
Posted by Claude Code on @evnchn's behalf.
Approving — I ran both repros from issue 6236 against main and against this branch: both symptoms flip. Snapshotting the path you actually registered, instead of re-reading a user-writable prop at teardown, is the right shape for this. Two zero-cost tightenings below, neither blocking.
Empirical before/after, and two optional tightenings
Base 3bb58aee (main) vs. this branch 4cfeb3d0, same interpreter, selected via PYTHONPATH; each run printed nicegui.__file__ to confirm which tree was loaded.
Repro 1 — .props('url="/api/my-upload"') then delete()
| base | this PR | |
|---|---|---|
| NiceGUI's own upload route leaked | True |
False |
app's own /api/my-upload survived |
False |
True |
Repro 2 — .props(remove='url') then card.delete()
| base | this PR | |
|---|---|---|
| teardown | raised KeyError 'url' |
completed |
upload still in client.elements |
True |
False |
| sibling created after it | stranded, is_deleted=False |
gone, is_deleted=True |
Repro 2 is the one that earns the fix on its own — the exception escaped _handle_delete into Client.remove_elements, whose loop is unguarded, so every element after the upload in deletion order was stranded too.
Also run on this branch, since CI was still queued at the time of writing: tests/test_upload.py → 17 passed / 0 skipped; mypy ./nicegui → clean over 245 files; pylint ./nicegui → 10.00/10; pre-commit run --all-files → all hooks pass.
Optional, non-blocking:
-
Line 75 still registers with
self._props['url']while line 127 removes withself._registered_url. Identical today — I checked that class-level default props are seeded inElement.__init__and then overwritten by line 61, so both sites see the same path even for a subclass carryingdefault_props='url=...'. But nothing structurally enforces the pairing: anything that later touches the prop between lines 62 and 75 would register one path and unregister another, which is this bug again. Passingself._registered_urlto@app.post(...)costs no extra lines and makes the pair unbreakable. -
A class-level
_registered_url: str = ''default would close the last teardown-raises path: if__init__aborts afterElement.__init__has already registered the element but before line 62 runs,_handle_deletenow raisesAttributeErrorwhere it used to raiseKeyError. Pre-existing in kind rather than anything this PR introduces — base fails the same way — but it is the same strand-the-siblings failure the PR is fixing.
On tests: CONTRIBUTING asks that a fix deliberately shipping without one say why in the Implementation section. Both symptoms here are assertable on observable behavior without touching internals — delete a container holding an upload whose url prop was changed, then assert a sibling element is actually gone — roughly the shape of the existing test_replace_upload. Either a test or a line in the PR body would close that out; entirely the maintainers' call, not something I'd hold the fix for.
Motivation
Fixes #6236
Implementation
Introduced a new variable:
upload._registered_url.I tested the the change using the example provided in #6236.
For the sake of being explicit, here is the output of that script
I am not sure whether writing a pytest for this case is necessary, so I will leave that up for the reviewer to decide.
Progress