presenton/electron/servers/fastapi/utils/path_helpers.py
sudipnext 3207422651 feat: add language parameter to decomposeDocuments API call
- 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.
2026-03-28 15:34:53 +05:45

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