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:
+31
-21
@@ -4,25 +4,36 @@ PyInstaller Konfiguration für DocuMentor
|
|||||||
Erstellt eine eigenständige Windows-Executable ohne Python-Installation
|
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
|
||||||
# Projektpfad
|
|
||||||
project_root = Path(SPECPATH)
|
# Projektpfad
|
||||||
src_path = project_root / 'src'
|
project_root = Path(SPECPATH)
|
||||||
|
src_path = project_root / 'src'
|
||||||
# Alle UI-Dateien sammeln
|
|
||||||
ui_files = []
|
# connectorx komplett sammeln (Python-Code, native .pyd und Metadaten)
|
||||||
for ui_file in (src_path / 'ui').glob('*.ui'):
|
# PyInstaller erkennt connectorx nicht automatisch, da es zur Laufzeit
|
||||||
ui_files.append((str(ui_file), 'ui'))
|
# von polars per importlib.import_module() geladen wird
|
||||||
|
cx_datas, cx_binaries, cx_hiddenimports = collect_all('connectorx')
|
||||||
a = Analysis(
|
|
||||||
[str(src_path / 'main.py')],
|
# Alle UI-Dateien sammeln
|
||||||
pathex=[str(src_path)],
|
ui_files = []
|
||||||
binaries=[],
|
for ui_file in (src_path / 'ui').glob('*.ui'):
|
||||||
datas=ui_files, # UI-Dateien einbinden
|
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(
|
||||||
|
[str(src_path / 'main.py')],
|
||||||
|
pathex=[str(src_path)],
|
||||||
|
binaries=cx_binaries,
|
||||||
|
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
@@ -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"
|
||||||
|
|||||||
@@ -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}")
|
||||||
|
|
||||||
|
|||||||
@@ -166,14 +166,16 @@ exe = EXE(
|
|||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
## PyInstaller .spec Konfiguration
|
## PyInstaller .spec Konfiguration
|
||||||
|
|
||||||
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
|
||||||
- **hiddenimports**: Alle notwendigen PySide6/Polars-Module
|
- **binaries**: Native Extensions (connectorx `.pyd`)
|
||||||
- **console=False**: GUI-Anwendung ohne Konsole
|
- **hiddenimports**: Alle notwendigen PySide6/Polars-Module
|
||||||
- **upx=True**: Kompression der Binaries
|
- **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
|
||||||
|
- **upx=True**: Kompression der Binaries
|
||||||
|
|
||||||
### Wichtige Einstellungen
|
### Wichtige Einstellungen
|
||||||
|
|
||||||
@@ -294,24 +296,49 @@ wine dist/DocuMentor/DocuMentor.exe
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### Problem: "Module not found" Fehler
|
### Problem: "Module not found" Fehler
|
||||||
|
|
||||||
|
**Lösung A** – Einfache Python-Module: Hidden imports in `DocuMentor.spec` ergänzen:
|
||||||
|
```python
|
||||||
|
hiddenimports=[
|
||||||
|
# ... existing
|
||||||
|
'missing_module',
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**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:
|
||||||
|
```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,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
**Lösung**: Hidden imports in `DocuMentor.spec` ergänzen:
|
### Problem: UI-Dateien oder Ressourcen nicht gefunden
|
||||||
```python
|
|
||||||
hiddenimports=[
|
**Lösung**: Prüfen ob datas korrekt konfiguriert. Ressource-Pfade müssen im Code PyInstaller-kompatibel aufgelöst werden (`sys._MEIPASS`):
|
||||||
# ... existing
|
```python
|
||||||
'missing_module',
|
# In DocuMentor.spec:
|
||||||
]
|
datas=[
|
||||||
```
|
('src/ui/*.ui', 'ui'),
|
||||||
|
('src/res/*', 'res'),
|
||||||
### Problem: UI-Dateien nicht gefunden
|
]
|
||||||
|
|
||||||
**Lösung**: Prüfen ob datas korrekt konfiguriert:
|
# Im Code:
|
||||||
```python
|
import sys
|
||||||
datas=[
|
from pathlib import Path
|
||||||
('src/ui/*.ui', 'ui'),
|
|
||||||
]
|
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
@@ -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",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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" },
|
||||||
|
|||||||
Reference in New Issue
Block a user