webbee 0.6.4__tar.gz → 0.6.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {webbee-0.6.4 → webbee-0.6.5}/PKG-INFO +1 -1
- {webbee-0.6.4 → webbee-0.6.5}/pyproject.toml +1 -1
- webbee-0.6.5/src/webbee/__init__.py +1 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/autoupdate.py +89 -6
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_autoupdate.py +41 -1
- webbee-0.6.4/src/webbee/__init__.py +0 -1
- {webbee-0.6.4 → webbee-0.6.5}/.github/workflows/publish.yml +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/.gitignore +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/CHANGELOG.md +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/LICENSE +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/README.md +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/install.sh +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/__main__.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/_launch.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/account.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/active_sessions.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/banner_art.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/boot.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/browser.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/checkpoints.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/cli.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/clipboard.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/clipboard_read.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/clipboard_session.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/coding_context.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/commands.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/config.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/consent.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/details.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/events.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/frames.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/home.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/home_view.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/http.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/instance_lock.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/intel/__init__.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/intel/graph.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/intel/outline.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/intel/query.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/intel/service.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/intel/store.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/intel/vectors.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/intel/watch.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/media.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/modals.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/mode_store.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/native_files.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/native_read.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/newtab_mode.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/output_pane.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/pricing_constants.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/queue_panel.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/reflow.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/remote.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/render.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/repl.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/repo.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/selection.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/session.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/sessions.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/sizing.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/slots.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/steer.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/stream.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/tab_store.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/tabs.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/thread.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/tier_store.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/todo_panel.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/tokens.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/tools.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/tui.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/update.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/urlopen.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/wallet.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/src/webbee/worktrees.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/__init__.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/conftest.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_account.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_active_sessions.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_audit_2026_07_25_findings.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_checkpoints.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_cli.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_clipboard.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_clipboard_read.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_coding_context.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_commands.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_config.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_cpc_contract_stable.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_cpc_outline.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_cross_loop_token.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_details.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_events.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_freeze_fix.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_home.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_home_view.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_home_view_mouse.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_inspect_page.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_instance_lock.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_intel_chunker.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_intel_embed.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_intel_graph.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_intel_no_numpy.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_intel_query.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_intel_service.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_intel_store.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_intel_vectors.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_intel_watch.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_launch.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_marathon.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_modals_e2e.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_mode_store.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_native_files.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_newtab_mode.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_packaging.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_probe_cost.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_queue_manage_0337.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_reflow.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_render.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_repl.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_repo.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_resilient_http.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_session.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_sessions.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_sizing.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_slots.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_steer.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_stream.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_tab_bar_stability.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_tab_restore_0337.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_tabs.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_terminal_ux_0336.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_thread.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_tier_invalidation.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_tier_store.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_todo_panel.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_tokens.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_tools.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_tools_vision_websearch.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_tui.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_tui_hardening.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_update.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_update_badge_0340.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_urlopen.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_version.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_version_badge_corner.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_wallet.py +0 -0
- {webbee-0.6.4 → webbee-0.6.5}/tests/test_worktrees.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.5"
|
|
@@ -12,6 +12,8 @@ import os
|
|
|
12
12
|
import shutil
|
|
13
13
|
import subprocess
|
|
14
14
|
import sys
|
|
15
|
+
import tempfile
|
|
16
|
+
import time
|
|
15
17
|
|
|
16
18
|
DIST = "webbee"
|
|
17
19
|
MCP = "imperal-mcp"
|
|
@@ -22,6 +24,10 @@ OFF_ENV = "WEBBEE_NO_AUTOUPDATE" # 1: never upgrade at the start (CI,
|
|
|
22
24
|
DONE_ENV = "WEBBEE_UPGRADED" # set on the restart after a self-upgrade: never twice in a row
|
|
23
25
|
LIGHT_ENV = "WEBBEE_LIGHT_PYTHON" # the old Python whose light install runs this release (webbee._launch)
|
|
24
26
|
STEP_TIMEOUT_S = 300
|
|
27
|
+
# one upgrade at a time per user: two webbees started together reinstalled one venv at once and the restart found it
|
|
28
|
+
# half-written (live 2026-10-25: "No module named 'pygments.styles'"); the second waits for the first
|
|
29
|
+
LOCK_WAIT_S = 2 * STEP_TIMEOUT_S + 60
|
|
30
|
+
LOCK_STALE_S = 3 * STEP_TIMEOUT_S
|
|
25
31
|
_WINDOWS = os.name == "nt"
|
|
26
32
|
# `python -c` puts the current folder first on sys.path: a webbee.py there would run in our place (security review).
|
|
27
33
|
# Dropped before any import -- `-I` is no way out, it also drops a `pip install --user` webbee.
|
|
@@ -104,14 +110,18 @@ def self_command(version: str, prefix: str = sys.prefix, executable: str = sys.e
|
|
|
104
110
|
light = env.get(LIGHT_ENV)
|
|
105
111
|
if light: # an old Python runs this release through its light install
|
|
106
112
|
return [light, "-m", "pip", "install", "--upgrade", "--no-cache-dir", spec]
|
|
107
|
-
return _install(tool_of(prefix), spec, uv, pipx, executable)
|
|
113
|
+
return _install(tool_of(prefix), spec, uv, pipx, executable, venv=DIST)
|
|
108
114
|
|
|
109
115
|
|
|
110
|
-
def _install(tool: str, spec: str, uv, pipx, python):
|
|
116
|
+
def _install(tool: str, spec: str, uv, pipx, python, venv: str = ""):
|
|
111
117
|
"""``spec`` installed by ``tool`` -- past its index cache: a release minutes old is missing from a cached index page
|
|
112
|
-
(live 2026-10-25: pip listed nothing newer than 0.5.13 while PyPI served 0.6.0).
|
|
118
|
+
(live 2026-10-25: pip listed nothing newer than 0.5.13 while PyPI served 0.6.0). A pipx venv that exists is upgraded
|
|
119
|
+
in place (``runpip``): ``install --force`` deletes the venv and builds it again, and a start in that window found
|
|
120
|
+
it half-written (live 2026-10-25)."""
|
|
113
121
|
if tool == "uv" and uv:
|
|
114
122
|
return [uv, "tool", "install", "--force", "--refresh", spec]
|
|
123
|
+
if tool == "pipx" and pipx and venv:
|
|
124
|
+
return [pipx, "runpip", venv, "install", "--upgrade", "--no-cache-dir", spec]
|
|
115
125
|
if tool == "pipx" and pipx:
|
|
116
126
|
return [pipx, "install", "--force", "--pip-args=--no-cache-dir", spec]
|
|
117
127
|
if tool == "pip" and python:
|
|
@@ -154,7 +164,7 @@ def mcp_command(inst: dict, newest, uv, pipx, wheel=None):
|
|
|
154
164
|
return None # PyPI unread: what works stays
|
|
155
165
|
spec = _spec(MCP_SPEC, MCP, newest, wheel) if newest else (MCP_SPEC if inst else MCP_GIT_SPEC)
|
|
156
166
|
tool = inst.get("tool") if inst else ("uv" if uv else "pipx" if pipx else "")
|
|
157
|
-
return _install(tool, spec, uv, pipx, inst.get("python") if inst else None)
|
|
167
|
+
return _install(tool, spec, uv, pipx, inst.get("python") if inst else None, venv=MCP if inst else "")
|
|
158
168
|
|
|
159
169
|
|
|
160
170
|
def restart_command(argv, env=os.environ, executable: str = sys.executable):
|
|
@@ -165,6 +175,55 @@ def restart_command(argv, env=os.environ, executable: str = sys.executable):
|
|
|
165
175
|
return [executable, "-c", _NO_CWD + "from webbee.cli import main; main()", *argv]
|
|
166
176
|
|
|
167
177
|
|
|
178
|
+
def _lock_path() -> str:
|
|
179
|
+
who = str(os.getuid()) if hasattr(os, "getuid") else (os.environ.get("USERNAME") or "user")
|
|
180
|
+
return os.path.join(tempfile.gettempdir(), f"webbee-upgrade-{who}.lock")
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _lock(path: str) -> bool:
|
|
184
|
+
"""This start's turn to upgrade: the lock file made by it alone (``O_EXCL``, any system); a lock older than any
|
|
185
|
+
upgrade takes is left over from a killed start and is taken over."""
|
|
186
|
+
for _ in range(2):
|
|
187
|
+
try:
|
|
188
|
+
fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
|
|
189
|
+
os.write(fd, f"{os.getpid()} {time.time():.0f}".encode())
|
|
190
|
+
os.close(fd)
|
|
191
|
+
return True
|
|
192
|
+
except FileExistsError:
|
|
193
|
+
try:
|
|
194
|
+
if time.time() - os.path.getmtime(path) < LOCK_STALE_S:
|
|
195
|
+
return False
|
|
196
|
+
os.remove(path)
|
|
197
|
+
except OSError:
|
|
198
|
+
return False
|
|
199
|
+
except OSError:
|
|
200
|
+
return True # no place for a lock: upgrade as before rather than never
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _unlock(path: str) -> None:
|
|
205
|
+
try:
|
|
206
|
+
os.remove(path)
|
|
207
|
+
except OSError:
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _wait_unlocked(path: str, sleep=time.sleep) -> None:
|
|
212
|
+
end = time.time() + LOCK_WAIT_S
|
|
213
|
+
while os.path.exists(path) and time.time() < end:
|
|
214
|
+
sleep(1)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def imports_ok(python: str, run=subprocess.run) -> bool:
|
|
218
|
+
"""The installed webbee starts: its modules import in its own Python (a half-written venv fails here, not at the
|
|
219
|
+
user's start)."""
|
|
220
|
+
try:
|
|
221
|
+
return run([python, "-c", _NO_CWD + "import webbee.cli, webbee.repl"], capture_output=True, text=True,
|
|
222
|
+
timeout=60).returncode == 0
|
|
223
|
+
except Exception:
|
|
224
|
+
return False
|
|
225
|
+
|
|
226
|
+
|
|
168
227
|
def _run(cmd, run=subprocess.run) -> bool:
|
|
169
228
|
try:
|
|
170
229
|
return run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, text=True,
|
|
@@ -174,22 +233,43 @@ def _run(cmd, run=subprocess.run) -> bool:
|
|
|
174
233
|
|
|
175
234
|
|
|
176
235
|
def upgrade_before_start(argv, current: str, say=print, fetch=latest, which=safe_which, run=subprocess.run,
|
|
177
|
-
execv=os.execv, env=os.environ, wheel=wheel_url
|
|
236
|
+
execv=os.execv, env=os.environ, wheel=wheel_url, lock_path=_lock_path,
|
|
237
|
+
sleep=time.sleep) -> list:
|
|
178
238
|
"""Bring webbee and the shell's imperal-mcp to their newest releases, then let the start go on; a self-upgrade
|
|
179
239
|
starts webbee again on the new release (never returns then). Returns what it did, for the log. Never raises."""
|
|
180
240
|
did: list = []
|
|
241
|
+
lock = ""
|
|
181
242
|
try:
|
|
182
243
|
if env.get(OFF_ENV) == "1":
|
|
183
244
|
return did
|
|
245
|
+
lock = lock_path() if lock_path else ""
|
|
246
|
+
if lock and not _lock(lock):
|
|
247
|
+
# another webbee is upgrading this install: wait for it, then start on what it installed
|
|
248
|
+
say("🐝 another webbee is updating -- waiting for it ...")
|
|
249
|
+
_wait_unlocked(lock, sleep)
|
|
250
|
+
lock = ""
|
|
251
|
+
newest = fetch(DIST)
|
|
252
|
+
if newest and _ver(newest) > _ver(current) and not env.get(DONE_ENV):
|
|
253
|
+
env[DONE_ENV] = "1"
|
|
254
|
+
again = restart_command(list(argv), env=env)
|
|
255
|
+
if _WINDOWS:
|
|
256
|
+
sys.exit(subprocess.call(again))
|
|
257
|
+
execv(again[0], again)
|
|
258
|
+
return did
|
|
184
259
|
uv, pipx = find_uv(which), which("pipx")
|
|
185
260
|
newest = fetch(DIST)
|
|
186
261
|
if newest and _ver(newest) > _ver(current) and not env.get(DONE_ENV):
|
|
187
262
|
cmd = self_command(newest, env=env, uv=uv, pipx=pipx, wheel=wheel)
|
|
188
263
|
if cmd:
|
|
189
264
|
say(f"🐝 updating webbee {current} -> {newest} ...")
|
|
190
|
-
|
|
265
|
+
python = env.get(LIGHT_ENV) or ""
|
|
266
|
+
ok = _run(cmd, run) and (python or imports_ok(sys.executable, run) or
|
|
267
|
+
(_run(cmd, run) and imports_ok(sys.executable, run)))
|
|
268
|
+
if ok:
|
|
191
269
|
did.append(f"webbee {newest}")
|
|
192
270
|
env[DONE_ENV] = "1" # the restart inherits it: never a second self-upgrade
|
|
271
|
+
_unlock(lock) # exec never returns: the lock goes before it, not in finally
|
|
272
|
+
lock = ""
|
|
193
273
|
again = restart_command(list(argv), env=env)
|
|
194
274
|
if _WINDOWS: # no exec on Windows: run it and pass its exit code on
|
|
195
275
|
sys.exit(subprocess.call(again))
|
|
@@ -211,5 +291,8 @@ def upgrade_before_start(argv, current: str, say=print, fetch=latest, which=safe
|
|
|
211
291
|
raise
|
|
212
292
|
except Exception:
|
|
213
293
|
pass
|
|
294
|
+
finally:
|
|
295
|
+
if lock:
|
|
296
|
+
_unlock(lock)
|
|
214
297
|
return did
|
|
215
298
|
|
|
@@ -30,7 +30,7 @@ def test_webbee_upgrades_itself_by_its_own_tool():
|
|
|
30
30
|
uv = au.self_command("0.6.0", prefix="/u/.local/share/uv/tools/webbee", env={}, uv="/bin/uv")
|
|
31
31
|
assert uv == ["/bin/uv", "tool", "install", "--force", "--refresh", "webbee==0.6.0"]
|
|
32
32
|
px = au.self_command("0.6.0", prefix="/h/.local/pipx/venvs/webbee", env={}, pipx="/bin/pipx")
|
|
33
|
-
assert px == ["/bin/pipx", "install", "--
|
|
33
|
+
assert px == ["/bin/pipx", "runpip", "webbee", "install", "--upgrade", "--no-cache-dir", "webbee==0.6.0"]
|
|
34
34
|
pip = au.self_command("0.6.0", prefix="/venv", executable="/venv/bin/python", env={})
|
|
35
35
|
assert pip == ["/venv/bin/python", "-m", "pip", "install", "--upgrade", "--no-cache-dir", "webbee==0.6.0"]
|
|
36
36
|
light = au.self_command("0.6.0", env={au.LIGHT_ENV: "/usr/bin/python3.8"}) # an old Python's light install
|
|
@@ -124,3 +124,43 @@ def test_a_release_is_installed_by_its_wheel_s_address_when_pypi_names_it():
|
|
|
124
124
|
assert got[-1] == f"imperal-mcp[code] @ {mcp_url}"
|
|
125
125
|
assert au.self_command("0.6.1", prefix="/v", executable="/v/bin/python", env={}, wheel=lambda d, v: None)[-1] \
|
|
126
126
|
== "webbee==0.6.1"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ── live 2026-10-25: two starts reinstalled one venv at once; the restart found it half-written ──
|
|
130
|
+
def test_pipx_upgrades_its_venv_in_place():
|
|
131
|
+
px = au.self_command("0.6.5", prefix="/h/.local/pipx/venvs/webbee", env={}, pipx="/bin/pipx")
|
|
132
|
+
assert px == ["/bin/pipx", "runpip", "webbee", "install", "--upgrade", "--no-cache-dir", "webbee==0.6.5"]
|
|
133
|
+
inst = {"tool": "pipx", "python": "/p/bin/python", "version": "0.8.2", "code": True}
|
|
134
|
+
assert au.mcp_command(inst, "0.8.3", None, "/bin/pipx") == [
|
|
135
|
+
"/bin/pipx", "runpip", "imperal-mcp", "install", "--upgrade", "--no-cache-dir", "imperal-mcp[code]==0.8.3"]
|
|
136
|
+
assert au.mcp_command({}, "0.8.3", None, "/bin/pipx")[:3] == ["/bin/pipx", "install", "--force"] # a new one
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_a_second_start_waits_for_the_first_upgrade_and_starts_on_it(tmp_path):
|
|
140
|
+
lock = tmp_path / "webbee-upgrade.lock"
|
|
141
|
+
lock.write_text("1 0") # another webbee is upgrading right now
|
|
142
|
+
run, execd, env, said = _Run(), [], {}, []
|
|
143
|
+
slept = []
|
|
144
|
+
|
|
145
|
+
def sleep(s):
|
|
146
|
+
slept.append(s)
|
|
147
|
+
lock.unlink() # ... and it finishes
|
|
148
|
+
au.upgrade_before_start(["--once"], "0.6.4", say=said.append, fetch={"webbee": "0.6.5"}.get, wheel=None,
|
|
149
|
+
which=_which({"pipx": "/bin/pipx"}), run=run, execv=lambda p, a: execd.append(a),
|
|
150
|
+
env=env, lock_path=lambda: str(lock), sleep=sleep)
|
|
151
|
+
assert run.calls == [] and slept and execd and env[au.DONE_ENV] == "1" # no second install, the new release
|
|
152
|
+
assert "another webbee is updating" in said[0]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_the_lock_is_released_before_the_restart_and_a_broken_install_is_done_again(tmp_path, monkeypatch):
|
|
156
|
+
lock = tmp_path / "webbee-upgrade.lock"
|
|
157
|
+
seen = []
|
|
158
|
+
checks = iter([False, True]) # the first install left it broken, the second is whole
|
|
159
|
+
monkeypatch.setattr(au, "imports_ok", lambda python, run=None: next(checks))
|
|
160
|
+
run = _Run()
|
|
161
|
+
au.upgrade_before_start([], "0.6.4", say=lambda m: None, fetch={"webbee": "0.6.5"}.get, wheel=None,
|
|
162
|
+
which=_which({"pipx": "/bin/pipx"}), run=run, env={}, lock_path=lambda: str(lock),
|
|
163
|
+
execv=lambda p, a: seen.append(lock.exists()))
|
|
164
|
+
assert seen == [False] # no lock left behind by exec
|
|
165
|
+
assert len([c for c in run.calls if "install" in c]) == 2
|
|
166
|
+
assert not lock.exists()
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.6.4"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|