- Updated the decomposeDocuments method in PresentationGenerationApi to accept an optional language parameter. - Modified the UploadPage component to pass the selected language from the config when calling the decomposeDocuments method.
214 lines
7.4 KiB
Python
214 lines
7.4 KiB
Python
"""
|
|
Path resolution utilities for handling different deployment environments.
|
|
|
|
Supports:
|
|
- Development: Normal relative paths
|
|
- Docker: Standard file system paths
|
|
- PyInstaller (Electron): Paths resolved via sys._MEIPASS
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
|
|
|
|
def get_resource_path(relative_path: str) -> str:
|
|
"""
|
|
Get absolute path to a read-only resource (bundled assets).
|
|
|
|
Works across different environments:
|
|
- Development: Uses current working directory
|
|
- Docker: Uses current working directory
|
|
- PyInstaller: Uses temporary extraction directory (sys._MEIPASS)
|
|
|
|
Args:
|
|
relative_path: Path relative to the application root
|
|
|
|
Returns:
|
|
Absolute path to the resource
|
|
|
|
Example:
|
|
>>> get_resource_path("static/icons/icon.svg")
|
|
'/path/to/static/icons/icon.svg'
|
|
"""
|
|
try:
|
|
# PyInstaller creates a temp folder and stores path in _MEIPASS
|
|
base_path = sys._MEIPASS
|
|
# Running in PyInstaller bundle
|
|
return os.path.join(base_path, relative_path)
|
|
except AttributeError:
|
|
# Running in normal Python (development or Docker)
|
|
base_path = os.path.abspath(".")
|
|
return os.path.join(base_path, relative_path)
|
|
|
|
|
|
def get_writable_path(relative_path: str) -> str:
|
|
"""
|
|
Get absolute path to a writable location (for cache, user data, etc.).
|
|
|
|
Works across different environments:
|
|
- Development: Uses current working directory
|
|
- Docker: Uses current working directory (volumes should be mounted)
|
|
- PyInstaller: Uses executable directory or falls back to temp directory
|
|
|
|
Args:
|
|
relative_path: Path relative to the writable base location
|
|
|
|
Returns:
|
|
Absolute path to a writable location
|
|
|
|
Example:
|
|
>>> get_writable_path("fastembed_cache")
|
|
'/writable/path/fastembed_cache'
|
|
"""
|
|
try:
|
|
# Check if running in PyInstaller bundle
|
|
base_path = sys._MEIPASS
|
|
|
|
# In packaged mode, try to use a writable location
|
|
# First try: directory where the executable is located
|
|
exe_dir = os.path.dirname(sys.executable)
|
|
writable_path = os.path.join(exe_dir, relative_path)
|
|
|
|
# Test if writable
|
|
try:
|
|
os.makedirs(writable_path, exist_ok=True)
|
|
# Try to create a test file to verify write access
|
|
test_file = os.path.join(writable_path, '.write_test')
|
|
try:
|
|
with open(test_file, 'w') as f:
|
|
f.write('test')
|
|
os.remove(test_file)
|
|
return writable_path
|
|
except (IOError, OSError):
|
|
pass
|
|
except (IOError, OSError):
|
|
pass
|
|
|
|
# Fallback: Use temp directory with app-specific subdirectory
|
|
temp_base = os.path.join(tempfile.gettempdir(), "presenton")
|
|
writable_path = os.path.join(temp_base, relative_path)
|
|
os.makedirs(writable_path, exist_ok=True)
|
|
return writable_path
|
|
|
|
except AttributeError:
|
|
# Running in normal Python (development or Docker)
|
|
# Use current directory - in Docker, volumes should be mounted
|
|
base_path = os.path.abspath(".")
|
|
writable_path = os.path.join(base_path, relative_path)
|
|
os.makedirs(writable_path, exist_ok=True)
|
|
return writable_path
|
|
|
|
|
|
def is_pyinstaller() -> bool:
|
|
"""
|
|
Check if the application is running in a PyInstaller bundle.
|
|
|
|
Returns:
|
|
True if running in PyInstaller, False otherwise
|
|
"""
|
|
return hasattr(sys, '_MEIPASS')
|
|
|
|
|
|
def is_docker() -> bool:
|
|
"""
|
|
Check if the application is running in a Docker container.
|
|
|
|
Returns:
|
|
True if running in Docker, False otherwise
|
|
"""
|
|
# Check for common Docker indicators
|
|
if os.path.exists('/.dockerenv'):
|
|
return True
|
|
|
|
# Check cgroup for docker
|
|
try:
|
|
with open('/proc/1/cgroup', 'rt') as f:
|
|
return 'docker' in f.read()
|
|
except Exception:
|
|
return False
|
|
|
|
|
|
def get_environment_type() -> str:
|
|
"""
|
|
Determine the current runtime environment.
|
|
|
|
Returns:
|
|
'pyinstaller', 'docker', or 'development'
|
|
"""
|
|
if is_pyinstaller():
|
|
return 'pyinstaller'
|
|
elif is_docker():
|
|
return 'docker'
|
|
else:
|
|
return 'development'
|
|
|
|
|
|
def patch_python_docx_templates():
|
|
"""
|
|
Patch python-docx template path resolution for PyInstaller bundles.
|
|
|
|
In PyInstaller bundles, python-docx cannot find template files using relative
|
|
paths from __file__. This function patches the template loading functions to
|
|
use sys._MEIPASS to locate templates in the bundle.
|
|
|
|
This function is safe to call in any environment:
|
|
- Docker/Development: Returns immediately without patching (no-op)
|
|
- PyInstaller: Patches the template loading functions
|
|
|
|
Note: Call before any code path that uses python-docx inside a PyInstaller bundle.
|
|
"""
|
|
# Only patch if running in PyInstaller bundle
|
|
# This check ensures Docker and development environments are unaffected
|
|
if not is_pyinstaller():
|
|
return
|
|
|
|
try:
|
|
# Import docx.parts.hdrftr - this will only succeed if python-docx is installed
|
|
# On Windows, python-docx might not be installed, so we catch ImportError
|
|
from docx.parts import hdrftr as hdrftr_module
|
|
|
|
# Patch _default_header_xml
|
|
if hasattr(hdrftr_module, '_default_header_xml'):
|
|
_original_default_header_xml = hdrftr_module._default_header_xml
|
|
|
|
def _patched_default_header_xml():
|
|
"""Patched function that resolves template path correctly in PyInstaller bundle."""
|
|
try:
|
|
template_path = os.path.join(sys._MEIPASS, 'docx', 'templates', 'default-header.xml')
|
|
if os.path.exists(template_path):
|
|
with open(template_path, 'rb') as f:
|
|
return f.read()
|
|
except Exception:
|
|
# If anything fails, fall back to original implementation
|
|
pass
|
|
# Fallback to original implementation
|
|
return _original_default_header_xml()
|
|
|
|
hdrftr_module._default_header_xml = _patched_default_header_xml
|
|
|
|
# Patch _default_footer_xml
|
|
if hasattr(hdrftr_module, '_default_footer_xml'):
|
|
_original_default_footer_xml = hdrftr_module._default_footer_xml
|
|
|
|
def _patched_default_footer_xml():
|
|
"""Patched function that resolves template path correctly in PyInstaller bundle."""
|
|
try:
|
|
template_path = os.path.join(sys._MEIPASS, 'docx', 'templates', 'default-footer.xml')
|
|
if os.path.exists(template_path):
|
|
with open(template_path, 'rb') as f:
|
|
return f.read()
|
|
except Exception:
|
|
# If anything fails, fall back to original implementation
|
|
pass
|
|
return _original_default_footer_xml()
|
|
|
|
hdrftr_module._default_footer_xml = _patched_default_footer_xml
|
|
except ImportError:
|
|
# python-docx is not installed (e.g., on Windows)
|
|
# This is expected and safe to ignore
|
|
pass
|
|
except Exception:
|
|
# Any other error - log it but don't crash
|
|
# The original code might still work without the patch
|
|
pass
|