From 4baba60f0a78886e37fae485a510f604304ba865 Mon Sep 17 00:00:00 2001 From: milanofthe Date: Thu, 9 Jul 2026 11:28:06 +0200 Subject: [PATCH] Unpin pathsim: resolve latest at runtime instead of build-time freeze --- pyproject.toml | 2 +- scripts/extract.py | 46 ++++++-------------------- src/lib/components/WelcomeModal.svelte | 18 ++++++++-- src/lib/constants/dependencies.ts | 7 ++-- 4 files changed, 28 insertions(+), 45 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 836c18e9..6253f3d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ dependencies = [ "flask-cors>=4.0", "numpy", "waitress>=3.0", - "pathsim==0.20.0", + "pathsim", ] [project.optional-dependencies] diff --git a/scripts/extract.py b/scripts/extract.py index c33f65f4..62bc76d1 100644 --- a/scripts/extract.py +++ b/scripts/extract.py @@ -604,46 +604,24 @@ class DependencyExtractor: def __init__(self, config_loader: ConfigLoader): self.config = config_loader - def _get_package_version(self, import_name: str) -> str | None: - """Get installed version of a package (strips local version identifiers).""" - try: - module = importlib.import_module(import_name) - version = getattr(module, '__version__', None) - if version: - # Strip local version identifier (e.g., +g22f8f27) - not supported by PyPI - version = version.split('+')[0] - return version - except ImportError: - return None - def extract(self) -> dict: - """Extract all dependency information including installed versions.""" + """Extract dependency configuration. + + Package specs are taken verbatim from requirements-pyodide.txt. + Versions are intentionally NOT pinned to the locally installed + package: the runtime resolves pathsim (and toolboxes) against PyPI + so pathview never lags behind toolbox requirements. + """ pyodide_config = self.config.load_pyodide_config() pyodide_packages = self.config.load_requirements("requirements-pyodide.txt") - # Get installed versions and create pinned package specs - extracted_versions = {} - for pkg in pyodide_packages: - version = self._get_package_version(pkg["import"]) - if version: - extracted_versions[pkg["import"]] = version - base_name = pkg["pip"].split(">=")[0].split("==")[0].split("<=")[0].split("<")[0].split(">")[0] - - # Only pin release versions (not dev versions which aren't on PyPI) - if ".dev" in version: - print(f" {base_name} {version} is dev version, keeping original spec: {pkg['pip']}") - else: - pkg["pip"] = f"{base_name}=={version}" - print(f" Pinned {base_name} to version {version}") - return { "pyodide": pyodide_config, - "packages": pyodide_packages, - "extracted_versions": extracted_versions + "packages": pyodide_packages } def update_pyproject(self, packages: list[dict], project_root: Path) -> None: - """Update pyproject.toml dependencies with pinned package versions. + """Update pyproject.toml dependencies with package specs. Adds/updates entries for packages from requirements-pyodide.txt while leaving all other dependencies (flask, numpy, etc.) untouched. @@ -655,7 +633,7 @@ def update_pyproject(self, packages: list[dict], project_root: Path) -> None: text = pyproject_path.read_text(encoding="utf-8") - # Build pip specs for managed packages (e.g. "pathsim==0.17.0") + # Build pip specs for managed packages (e.g. "pathsim") new_specs: list[str] = [] for pkg in packages: base_name = pkg["pip"].split(">=")[0].split("==")[0].split("<=")[0].split("<")[0].split(">")[0] @@ -864,7 +842,6 @@ def write_dependencies(self, data: dict) -> None: """Write dependencies.ts file.""" pyodide = data.get("pyodide", {}) packages = data.get("packages", []) - extracted_versions = data.get("extracted_versions", {}) # Read PathView version from package.json (at project root, 2 levels up from src/lib) package_json_path = self.output_dir.parent.parent / "package.json" @@ -887,9 +864,6 @@ def write_dependencies(self, data: dict) -> None: "", f"export const PYODIDE_PRELOAD = {json.dumps(pyodide.get('preload', []))} as const;", "", - "/** Package versions extracted at build time (pinned for runtime) */", - f"export const EXTRACTED_VERSIONS: Record = {json.dumps(extracted_versions)};", - "", "export interface PackageConfig {", " pip: string;", " pre: boolean;", diff --git a/src/lib/components/WelcomeModal.svelte b/src/lib/components/WelcomeModal.svelte index 4c0eaa53..22e1eaf8 100644 --- a/src/lib/components/WelcomeModal.svelte +++ b/src/lib/components/WelcomeModal.svelte @@ -4,7 +4,8 @@ import { fade, fly } from 'svelte/transition'; import { cubicOut } from 'svelte/easing'; import Icon from '$lib/components/icons/Icon.svelte'; - import { PATHVIEW_VERSION, EXTRACTED_VERSIONS } from '$lib/constants/dependencies'; + import { PATHVIEW_VERSION } from '$lib/constants/dependencies'; + import { getCachedPathsimVersion } from '$lib/toolbox/pathsimVersion'; import { BRAND } from '$lib/constants/brand'; import { startGuidedTour, type TourId } from '$lib/tours'; @@ -36,6 +37,7 @@ ]; let isDark = $state(true); + let pathsimVersion = $state(getCachedPathsimVersion()); onMount(() => { const updateTheme = () => { @@ -46,7 +48,17 @@ const observer = new MutationObserver(updateTheme); observer.observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] }); - return () => observer.disconnect(); + // The pathsim version is resolved at runtime (after the Python engine + // boots), which may happen after this modal renders — poll until known. + const versionPoll = setInterval(() => { + pathsimVersion = getCachedPathsimVersion(); + if (pathsimVersion) clearInterval(versionPoll); + }, 500); + + return () => { + observer.disconnect(); + clearInterval(versionPoll); + }; }); function handleNew() { @@ -99,7 +111,7 @@