Fix: PyInstaller-Bundle für installierte Version repariert (connectorx, SQL-Ressourcen)

- connectorx via collect_all() eingebunden statt hiddenimports (Rust-PYD + __init__.py + Metadaten als Einheit)
- SQL/CSV-Ressourcen (src/res/) ins PyInstaller-Bundle aufgenommen
- Pfadauflösung in database.py auf sys._MEIPASS umgestellt für installierten Modus
- connectorx als explizite Abhängigkeit in pyproject.toml ergänzt
- Dokumentation (windows_distribution.md) um collect_all-Pattern und _MEIPASS-Hinweise erweitert
- Version auf 1.0.0 aktualisiert, Hersteller-Informationen ergänzt
This commit is contained in:
2026-02-15 19:51:58 +01:00
parent ec33a5b586
commit affba2a9ca
7 changed files with 100 additions and 54 deletions
+14 -4
View File
@@ -6,6 +6,7 @@ Erstellt eine eigenständige Windows-Executable ohne Python-Installation
import sys import sys
from pathlib import Path from pathlib import Path
from PyInstaller.utils.hooks import collect_all
block_cipher = None block_cipher = None
@@ -13,16 +14,26 @@ block_cipher = None
project_root = Path(SPECPATH) project_root = Path(SPECPATH)
src_path = project_root / 'src' src_path = project_root / 'src'
# connectorx komplett sammeln (Python-Code, native .pyd und Metadaten)
# PyInstaller erkennt connectorx nicht automatisch, da es zur Laufzeit
# von polars per importlib.import_module() geladen wird
cx_datas, cx_binaries, cx_hiddenimports = collect_all('connectorx')
# Alle UI-Dateien sammeln # Alle UI-Dateien sammeln
ui_files = [] ui_files = []
for ui_file in (src_path / 'ui').glob('*.ui'): for ui_file in (src_path / 'ui').glob('*.ui'):
ui_files.append((str(ui_file), 'ui')) ui_files.append((str(ui_file), 'ui'))
# Ressource-Dateien (SQL, CSV) einbinden
res_files = []
for res_file in (src_path / 'res').glob('*'):
res_files.append((str(res_file), 'res'))
a = Analysis( a = Analysis(
[str(src_path / 'main.py')], [str(src_path / 'main.py')],
pathex=[str(src_path)], pathex=[str(src_path)],
binaries=[], binaries=cx_binaries,
datas=ui_files, # UI-Dateien einbinden datas=ui_files + res_files + cx_datas,
hiddenimports=[ hiddenimports=[
'PySide6.QtCore', 'PySide6.QtCore',
'PySide6.QtGui', 'PySide6.QtGui',
@@ -32,8 +43,7 @@ a = Analysis(
'pydantic_yaml', 'pydantic_yaml',
'polars', 'polars',
'pyarrow', 'pyarrow',
'connectorx', ] + cx_hiddenimports,
],
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},
runtime_hooks=[], runtime_hooks=[],
+2 -2
View File
@@ -4,8 +4,8 @@
<!-- Paket-Definition (ersetzt Product in v4) --> <!-- Paket-Definition (ersetzt Product in v4) -->
<Package <Package
Name="DocuMentor" Name="DocuMentor"
Version="0.1.0" Version="1.0.0"
Manufacturer="Ihr Name/Firma" Manufacturer="Vitali Graf / Software- und Datenbankentwicklung"
UpgradeCode="F498B66C-726D-44AA-95F4-CB4FBDCEF26E" UpgradeCode="F498B66C-726D-44AA-95F4-CB4FBDCEF26E"
Language="1031" Language="1031"
Compressed="yes" Compressed="yes"
+3 -3
View File
@@ -70,11 +70,11 @@ VSVersionInfo(
[ [
StringTable( StringTable(
'040904B0', # Deutsch (0x0409 = Englisch, 0x0407 = Deutsch), Unicode '040904B0', # Deutsch (0x0409 = Englisch, 0x0407 = Deutsch), Unicode
[StringStruct('CompanyName', 'Ihr Name/Organisation'), [StringStruct('CompanyName', 'Vitali Graf / Software- und Datenbankentwicklung'),
StringStruct('FileDescription', '{description}'), StringStruct('FileDescription', '{description}'),
StringStruct('FileVersion', '{version}'), StringStruct('FileVersion', '{version}'),
StringStruct('InternalName', '{name}'), StringStruct('InternalName', '{name}'),
StringStruct('LegalCopyright', '© {year} Ihr Name. Alle Rechte vorbehalten.'), StringStruct('LegalCopyright', '© {year} Vitali Graf. Alle Rechte vorbehalten.'),
StringStruct('OriginalFilename', '{name}.exe'), StringStruct('OriginalFilename', '{name}.exe'),
StringStruct('ProductName', '{name}'), StringStruct('ProductName', '{name}'),
StringStruct('ProductVersion', '{version}')]) StringStruct('ProductVersion', '{version}')])
@@ -88,7 +88,7 @@ VSVersionInfo(
version_info_path = project_root / "version_info.txt" version_info_path = project_root / "version_info.txt"
version_info_path.write_text(version_info_content, encoding='utf-8') version_info_path.write_text(version_info_content, encoding='utf-8')
print(f"✓ version_info.txt erstellt") print("✓ version_info.txt erstellt")
print(f" Version: {version}") print(f" Version: {version}")
print(f" Datei: {version_info_path}") print(f" Datei: {version_info_path}")
+32 -5
View File
@@ -170,8 +170,10 @@ exe = EXE(
Die Datei `DocuMentor.spec` enthält: Die Datei `DocuMentor.spec` enthält:
- **datas**: UI-Dateien (.ui) werden mitgepackt - **datas**: UI-Dateien (.ui), Ressource-Dateien (SQL, CSV) und connectorx-Package
- **binaries**: Native Extensions (connectorx `.pyd`)
- **hiddenimports**: Alle notwendigen PySide6/Polars-Module - **hiddenimports**: Alle notwendigen PySide6/Polars-Module
- **collect_all('connectorx')**: Sammelt Python-Code, native `.pyd`-Extension und Metadaten als Einheit (nötig, da `polars` connectorx erst zur Laufzeit per `importlib.import_module()` lädt)
- **console=False**: GUI-Anwendung ohne Konsole - **console=False**: GUI-Anwendung ohne Konsole
- **upx=True**: Kompression der Binaries - **upx=True**: Kompression der Binaries
@@ -296,7 +298,7 @@ wine dist/DocuMentor/DocuMentor.exe
### Problem: "Module not found" Fehler ### Problem: "Module not found" Fehler
**Lösung**: Hidden imports in `DocuMentor.spec` ergänzen: **Lösung A** Einfache Python-Module: Hidden imports in `DocuMentor.spec` ergänzen:
```python ```python
hiddenimports=[ hiddenimports=[
# ... existing # ... existing
@@ -304,13 +306,38 @@ hiddenimports=[
] ]
``` ```
### Problem: UI-Dateien nicht gefunden **Lösung B** Packages mit nativen Extensions (Rust/C, z.B. `connectorx`):
`hiddenimports` allein reicht nicht, da PyInstaller die `__init__.py` ins PYZ-Archiv packt, aber die `.pyd`-Extension separat im Dateisystem erwartet. Stattdessen `collect_all` verwenden:
**Lösung**: Prüfen ob datas korrekt konfiguriert:
```python ```python
from PyInstaller.utils.hooks import collect_all
cx_datas, cx_binaries, cx_hiddenimports = collect_all('problematic_package')
a = Analysis(
# ...
binaries=cx_binaries,
datas=cx_datas,
hiddenimports=cx_hiddenimports,
)
```
### Problem: UI-Dateien oder Ressourcen nicht gefunden
**Lösung**: Prüfen ob datas korrekt konfiguriert. Ressource-Pfade müssen im Code PyInstaller-kompatibel aufgelöst werden (`sys._MEIPASS`):
```python
# In DocuMentor.spec:
datas=[ datas=[
('src/ui/*.ui', 'ui'), ('src/ui/*.ui', 'ui'),
('src/res/*', 'res'),
] ]
# Im Code:
import sys
from pathlib import Path
if hasattr(sys, "_MEIPASS"):
res_path = Path(sys._MEIPASS) / "res" / "data.sql"
else:
res_path = Path(__file__).parents[3] / "src" / "res" / "data.sql"
``` ```
### Problem: Executable zu groß ### Problem: Executable zu groß
+2 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "DocuMentor" name = "DocuMentor"
version = "0.1.0" version = "1.0.0"
description = "Professionelle XSL-Transformations-Verwaltung und PDF-Generierung" description = "Professionelle XSL-Transformations-Verwaltung und PDF-Generierung"
readme = "README.md" readme = "README.md"
license = {text = "MIT"} license = {text = "MIT"}
@@ -9,6 +9,7 @@ dependencies = [
"pydantic-settings>=2.12.0", "pydantic-settings>=2.12.0",
"pyside6>=6.10.1", "pyside6>=6.10.1",
"polars[connectorx,pyarrow]>=1.37.0", "polars[connectorx,pyarrow]>=1.37.0",
"connectorx>=0.4.0",
"pydantic-yaml>=1.6.0", "pydantic-yaml>=1.6.0",
"psutil>=6.1.1", "psutil>=6.1.1",
] ]
+7 -1
View File
@@ -5,6 +5,7 @@ Dieses Mixin enthält alle Methoden zur PostgreSQL-Datenbankanbindung
und Datenverarbeitung für das MainWindow. und Datenverarbeitung für das MainWindow.
""" """
import sys
import time import time
import logging import logging
from pathlib import Path from pathlib import Path
@@ -162,7 +163,12 @@ class DatabaseMixin:
tuple[str, str]|tuple[None, None]: (sql_query, connection_string) oder (None, None) bei Fehler tuple[str, str]|tuple[None, None]: (sql_query, connection_string) oder (None, None) bei Fehler
""" """
try: try:
sql_file_path = Path("src/res/data.sql") # PyInstaller entpackt Ressourcen nach sys._MEIPASS;
# im Entwicklungsmodus liegt die Datei relativ zum Repo-Root
if hasattr(sys, "_MEIPASS"):
sql_file_path = Path(sys._MEIPASS) / "res" / "data.sql" # type: ignore[attr-defined]
else:
sql_file_path = Path(__file__).parents[3] / "src" / "res" / "data.sql"
if not sql_file_path.exists(): if not sql_file_path.exists():
QMessageBox.critical(self, "Fehler", f"SQL-Datei nicht gefunden: {sql_file_path}") QMessageBox.critical(self, "Fehler", f"SQL-Datei nicht gefunden: {sql_file_path}")
return None, None return None, None
Generated
+3 -1
View File
@@ -34,9 +34,10 @@ wheels = [
[[package]] [[package]]
name = "documentor" name = "documentor"
version = "0.1.0" version = "1.0.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "connectorx" },
{ name = "polars", extra = ["connectorx", "pyarrow"] }, { name = "polars", extra = ["connectorx", "pyarrow"] },
{ name = "psutil" }, { name = "psutil" },
{ name = "pydantic-settings" }, { name = "pydantic-settings" },
@@ -53,6 +54,7 @@ dev = [
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "connectorx", specifier = ">=0.4.0" },
{ name = "polars", extras = ["connectorx", "pyarrow"], specifier = ">=1.37.0" }, { name = "polars", extras = ["connectorx", "pyarrow"], specifier = ">=1.37.0" },
{ name = "psutil", specifier = ">=6.1.1" }, { name = "psutil", specifier = ">=6.1.1" },
{ name = "pydantic-settings", specifier = ">=2.12.0" }, { name = "pydantic-settings", specifier = ">=2.12.0" },