medikeep.spec
Rama main · réplica de lectura del repositorio original.
# -*- mode: python ; coding: utf-8 -*-
"""
PyInstaller spec file for MediKeep Windows EXE.
This spec file builds a standalone Windows executable with:
- FastAPI backend bundled
- SQLite database support
- PDF text extraction (Poppler + Tesseract)
- All dependencies included
- Frontend static files
Build command:
pyinstaller medikeep.spec
Output:
dist/MediKeep.exe - Standalone executable
dist/_internal/ - Required libraries and data files
"""
import os
import sys
from pathlib import Path
# Get the project root directory
project_root = Path(SPECPATH).resolve()
# Define data files to include
datas = []
# Add the app package as data files (CRITICAL: Must be included for imports to work)
if (project_root / 'app').exists():
datas.append(('app', 'app'))
print("[OK] Including app package")
# Add system tray icon for the packaged EXE
tray_icon = project_root / 'frontend' / 'public' / 'icon-64.png'
if tray_icon.exists():
datas.append(('frontend/public/icon-64.png', 'frontend/build'))
print("[OK] Including tray icon (icon-64.png)")
# Add frontend build if it exists (Create React App builds to 'build' directory)
if (project_root / 'frontend' / 'build').exists():
datas.append(('frontend/build', 'frontend/build'))
print("[OK] Including frontend build")
else:
print("[SKIP] Frontend not built - run 'npm run build' in frontend/ to include")
# Add Alembic migrations if they exist
if (project_root / 'alembic').exists():
datas.append(('alembic', 'alembic'))
print("[OK] Including Alembic migrations")
# Add .env.example if it exists
if (project_root / '.env.example').exists():
datas.append(('.env.example', '.'))
print("[OK] Including .env.example")
# Define hidden imports (modules not automatically detected)
hiddenimports = [
# MediKeep app modules (CRITICAL: Must be explicitly imported)
'app.main',
'app.core.config',
'app.core.database',
'app.core.logging_config',
'app.core.windows_config',
'app.core.external_binaries',
'app.core.system_tray',
# Alembic (for database migrations)
'alembic',
'alembic.command',
'alembic.config',
'alembic.script',
'alembic.runtime',
'alembic.runtime.migration',
'alembic.runtime.environment',
# FastAPI and dependencies
'uvicorn.logging',
'uvicorn.loops',
'uvicorn.loops.auto',
'uvicorn.protocols',
'uvicorn.protocols.http',
'uvicorn.protocols.http.auto',
'uvicorn.protocols.websockets',
'uvicorn.protocols.websockets.auto',
'uvicorn.lifespan',
'uvicorn.lifespan.on',
# SQLAlchemy and database
'sqlalchemy.ext.baked',
'sqlalchemy.sql.default_comparator',
# Pydantic
'pydantic.deprecated.decorator',
# PDF processing
'pdfplumber',
'pdf2image',
'pytesseract',
'PIL._imagingtk',
'PIL._tkinter_finder',
# System tray
'pystray',
'pystray._win32',
# Other critical imports
'python_dotenv',
]
# Binary files to exclude (reduce size)
excludes = [
# Exclude development/testing tools
'pytest',
'coverage',
'black',
'pylint',
'mypy',
# Exclude unused modules
'tkinter',
'matplotlib',
'IPython',
'jupyter',
# Exclude server-only databases
'psycopg2', # PostgreSQL - not needed in Windows EXE (uses SQLite)
'asyncpg',
]
# Analysis: Collect all Python files and dependencies
a = Analysis(
['run.py'], # Entry point
pathex=[str(project_root)],
binaries=[],
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=excludes,
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=None,
noarchive=False,
)
# PYZ: Create Python archive
pyz = PYZ(
a.pure,
a.zipped_data,
cipher=None,
)
# EXE: Create executable
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True, # Use COLLECT for multi-file distribution
name='MediKeep',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True, # Compress with UPX
console=False, # Windowed mode - runs in background with system tray
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon='medikeep.ico',
version_file=None, # TODO: Add version info when available
)
# Get version directly from config.py file without importing
# This avoids circular import issues during build
import re
config_file = project_root / 'app' / 'core' / 'config.py'
with open(config_file, 'r') as f:
config_content = f.read()
version_match = re.search(r'VERSION:\s*str\s*=\s*["\']([^"\']+)["\']', config_content)
if version_match:
APP_VERSION = version_match.group(1)
else:
APP_VERSION = "0.0.0" # Fallback
print("WARNING: Could not extract version from config.py")
print(f"Building MediKeep version {APP_VERSION}")
# COLLECT: Gather all files into distribution folder with version in folder name
coll = COLLECT(
exe,
a.binaries,
a.zipfiles,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name=f'MediKeep-v{APP_VERSION}',
)
# Post-build notes:
# After building, you need to manually add to dist/MediKeep/:
#
# 1. External binaries (PDF text extraction):
# - poppler/bin/ (pdftotext.exe, pdfimages.exe, etc.)
# - tesseract/ (tesseract.exe)
# - tesseract/tessdata/ (eng.traineddata, osd.traineddata)
#
# 2. Frontend (if not already included):
# - frontend/dist/ (React build output)
#
# Directory structure should be:
# dist/
# MediKeep/
# MediKeep.exe
# _internal/
# (Python libraries, dependencies)
# poppler/
# bin/
# pdftotext.exe
# pdfimages.exe
# pdfinfo.exe
# tesseract/
# tesseract.exe
# tessdata/
# eng.traineddata
# osd.traineddata
# frontend/
# dist/
# (React build files)