From 524cdfda9a0325782bda18a4a3f8b5da8577759a Mon Sep 17 00:00:00 2001 From: Nguyen HP Laptop Date: Sat, 23 May 2026 12:05:33 -0400 Subject: [PATCH] 05/23 Phase 8 --- .claude/settings.local.json | 4 +- .gitignore | 8 ++-- CLAUDE.md | 15 ++++--- assets/icon.png | Bin 0 -> 243 bytes core/exporter.py | 4 +- core/paths.py | 33 ++++++++++++++ data/lottosight.db | Bin 557056 -> 557056 bytes db/database.py | 6 +-- lottosight.spec | 87 ++++++++++++++++++++++++++++++++++++ main.py | 10 +++-- 10 files changed, 149 insertions(+), 18 deletions(-) create mode 100644 assets/icon.png create mode 100644 core/paths.py create mode 100644 lottosight.spec diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 0e41e08..9780c4e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -8,7 +8,9 @@ "Bash(python -m pytest tests/test_predictor.py -v)", "Bash(python -m pytest)", "Bash(python -m pytest tests/test_settings.py -v)", - "Bash(python -m pytest tests/ -v --tb=short)" + "Bash(python -m pytest tests/ -v --tb=short)", + "Bash(python -m pytest tests/ -q --tb=short)", + "Bash(python -m PyInstaller lottosight.spec --clean)" ] } } diff --git a/.gitignore b/.gitignore index 36b13f1..00118ba 100644 --- a/.gitignore +++ b/.gitignore @@ -28,10 +28,12 @@ share/python-wheels/ MANIFEST # PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest -*.spec +# *.spec intentionally NOT ignored — lottosight.spec is committed + +# LottoSight runtime artifacts +data/*.db +exports/ # Installer logs pip-log.txt diff --git a/CLAUDE.md b/CLAUDE.md index 6c1759b..d92ce39 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -325,11 +325,16 @@ All actions are logged to console and optionally to a log file: --- -### 🔲 Phase 8 — Packaging -- [ ] Write PyInstaller `.spec` file -- [ ] Test build on target OS (Windows / Mac / Linux) -- [ ] Bundle SQLite DB, assets, exports folder -- [ ] Test packaged app fresh install (no Python required) +### ✅ Phase 8 — Packaging +- [x] Write `core/paths.py` — `user_data_dir()` + `bundled_asset()` helpers (frozen-aware) +- [x] Update `db/database.py` + `core/exporter.py` to use `user_data_dir()` so DB + exports land next to the .exe when frozen +- [x] Update `main.py` to use `bundled_asset()` for icon lookup +- [x] Write `lottosight.spec` — directory build, no console, bundles `assets/`, matplotlib data, openpyxl templates, APScheduler hidden imports +- [x] Build verified: `pyinstaller lottosight.spec --clean` succeeds → `dist/LottoSight/` (~115 MB) +- [x] `assets/icon.png` confirmed present in `dist/LottoSight/_internal/assets/` +- [x] Fix `.gitignore` — un-ignore `*.spec`, add `data/*.db` + `exports/` +- [x] 165/165 tests still passing after paths refactor +- [ ] Test packaged app on a clean machine (no Python installed) --- diff --git a/assets/icon.png b/assets/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..3ef09f7ca8fe7b9c8a36a6a4a53d7f2d72bee081 GIT binary patch literal 243 zcmV*BdX@Vvw3Dl;!HO)^pK|cfyJImxw`#0T(hTRzD?$nA~humJKxVb9s_KNL? tTtB4Ppow;jiY=^SPc7Qy>;CJg%K>PNjQO48-fsW^002ovPDHLkV1lqfW$XX| literal 0 HcmV?d00001 diff --git a/core/exporter.py b/core/exporter.py index c4085e8..446e44c 100644 --- a/core/exporter.py +++ b/core/exporter.py @@ -18,9 +18,9 @@ import openpyxl from openpyxl.styles import Alignment, Font, PatternFill from db.models import get_draws_with_game, get_game_by_id, get_predictions +from core.paths import user_data_dir -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -EXPORTS_DIR = os.path.join(BASE_DIR, "exports") +EXPORTS_DIR = os.path.join(user_data_dir(), "exports") def ensure_exports_dir(): diff --git a/core/paths.py b/core/paths.py new file mode 100644 index 0000000..13296dd --- /dev/null +++ b/core/paths.py @@ -0,0 +1,33 @@ +""" +core/paths.py +------------- +Runtime path resolution that works both in development and when +frozen by PyInstaller (--onefile or directory build). + + user_data_dir() — mutable data root (DB, exports) — next to the .exe + bundled_asset(rel) — read-only asset path (icon etc.) — extracted bundle +""" + +import os +import sys + + +def user_data_dir() -> str: + """Root for mutable user data. Next to the .exe when frozen.""" + if getattr(sys, "frozen", False): + return os.path.dirname(sys.executable) + # development: two levels up from core/paths.py → project root + return os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + + +def bundled_asset(relative_path: str) -> str: + """Absolute path to a read-only bundled asset (icon, etc.). + + In a onefile build sys._MEIPASS is the temp extraction dir; + in a directory build it equals the exe directory. + """ + if getattr(sys, "frozen", False): + base = sys._MEIPASS # type: ignore[attr-defined] + else: + base = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + return os.path.join(base, relative_path) diff --git a/data/lottosight.db b/data/lottosight.db index f3f3ea14407fca28abd30da32c394e8c2de110ad..1c30682311a9f93b7a89c86cdcc8082354c444ce 100644 GIT binary patch delta 162 zcmZo@P-<#j4JZE^O^Q3GR6tLi7G8CHBtIB6;X=PwuT$-GmT3l=_IGM3uRRAo+c>*eE rATW7OpAuM|Df@J&VDrD8_J2K$K+FWh%s|Wn#H>Kfw*6lZJJV+X<$*SQ delta 94 zcmZo@P-