diff --git a/qualibrate_config/core/migration/migrations/v3_v4.py b/qualibrate_config/core/migration/migrations/v3_v4.py index 2460e7f..e982d8e 100644 --- a/qualibrate_config/core/migration/migrations/v3_v4.py +++ b/qualibrate_config/core/migration/migrations/v3_v4.py @@ -1,5 +1,5 @@ +import sys from copy import deepcopy -from importlib.util import find_spec from pathlib import Path from qualibrate_config.core.migration.migrations.base import MigrateBase @@ -22,13 +22,18 @@ def backward(data: RawConfigType, config_path: Path) -> RawConfigType: static_files = app.get("static_site_files") if static_files is not None: return {"qualibrate": qualibrate, **data} - app_module = find_spec("qualibrate_app") - if app_module is None or app_module.origin is None: - return {"qualibrate": qualibrate, **data} - module_path = Path(app_module.origin) - app["static_site_files"] = str( - module_path.parents[1] / "qualibrate_static" - ) + # Walk sys.path to find qualibrate_app's install location. Nuitka-safe + # alternative to find_spec("qualibrate_app").origin (which returns a + # phantom path under obfuscated builds). Semantics preserved: find the + # qualibrate_app package dir, go up one level (to site-packages), and + # append "qualibrate_static". + for entry in sys.path: + qa_dir = Path(entry) / "qualibrate_app" + if qa_dir.is_dir(): + app["static_site_files"] = str( + qa_dir.parent / "qualibrate_static" + ) + break return {"qualibrate": qualibrate, **data} @staticmethod diff --git a/qualibrate_config/models/q_app.py b/qualibrate_config/models/q_app.py index 9f00370..136bd43 100644 --- a/qualibrate_config/models/q_app.py +++ b/qualibrate_config/models/q_app.py @@ -1,4 +1,4 @@ -from importlib.util import find_spec +import sys from pathlib import Path from typing import Annotated @@ -12,10 +12,20 @@ def get_default_static_path() -> Path | None: - app_module = find_spec("qualibrate.app") - if app_module is None or app_module.origin is None: - return None - return Path(app_module.origin).resolve().parent / "qualibrate_static" + """Locate the qualibrate frontend static files. + + Walks sys.path (CPython's list of directories where packages live) rather + than relying on __file__ / find_spec().origin / importlib.resources, all + of which Nuitka's custom loader corrupts with phantom paths in obfuscated + builds. sys.path is maintained by CPython itself and Nuitka does not + intercept it, so this lookup works identically for source and compiled + installs. + """ + for entry in sys.path: + candidate = Path(entry) / "qualibrate" / "app" / "qualibrate_static" + if candidate.is_dir(): + return candidate + return None class QualibrateAppConfig(BaseConfig): diff --git a/tests/unit/test_cli/test_migrations/test_v3_v4.py b/tests/unit/test_cli/test_migrations/test_v3_v4.py index b03737c..fbb05f6 100644 --- a/tests/unit/test_cli/test_migrations/test_v3_v4.py +++ b/tests/unit/test_cli/test_migrations/test_v3_v4.py @@ -1,6 +1,5 @@ +import sys from copy import deepcopy -from importlib.util import find_spec -from pathlib import Path from qualibrate_config.core.migration.migrations import v3_v4 @@ -79,14 +78,14 @@ def test_migrate_v4_v3_without_static_app_exists(mocker, tmp_path): config_v4 = deepcopy(CONFIG_V3) config_v4["qualibrate"]["version"] = 4 config_v4["qualibrate"]["app"].pop("static_site_files") - config_spec = find_spec("qualibrate_config") - mocker.patch( - "qualibrate_config.core.migration.migrations.v3_v4.find_spec", - return_value=config_spec, - ) + # Simulate `qualibrate_app` being installed at tmp_path by creating the + # package dir and pointing sys.path at tmp_path. The migration's sys.path + # walk will then find it and derive the static_site_files from its parent. + (tmp_path / "qualibrate_app").mkdir() + mocker.patch.object(sys, "path", [str(tmp_path)]) config_v3_migrated = v3_v4.Migrate.backward(config_v4, tmp_path) config_v3_expected = deepcopy(CONFIG_V3) config_v3_expected["qualibrate"]["app"]["static_site_files"] = str( - Path(config_spec.origin).parents[1] / "qualibrate_static" + tmp_path / "qualibrate_static" ) assert config_v3_expected == config_v3_migrated