Source code for sqlalchemy_jdbcapi.jdbc.jvm

"""
JVM management and initialization.
"""

from __future__ import annotations

import logging
import os
from pathlib import Path
from typing import Any

from .driver_manager import get_classpath_with_drivers
from .exceptions import JVMNotStartedError

logger = logging.getLogger(__name__)

_jvm_started = False


[docs] def get_classpath( auto_download: bool = True, databases: list[str] | None = None, ) -> list[Path]: """ Get JDBC driver classpath from environment, auto-download, or both. Args: auto_download: Whether to auto-download recommended JDBC drivers. databases: List of database names for auto-download (e.g., ['postgresql', 'mysql']). If None and auto_download is True, downloads all recommended drivers. Returns: List of paths to JDBC driver JAR files. """ # Get manual classpath from environment manual_classpath = [] classpath_env = os.environ.get("CLASSPATH", "") if not classpath_env: classpath_env = os.environ.get("JDBC_DRIVER_PATH", "") if classpath_env: for path_str in classpath_env.split(os.pathsep): path = Path(path_str) if path.exists(): manual_classpath.append(path) else: logger.warning(f"Classpath entry not found: {path}") # Combine manual and auto-downloaded drivers return get_classpath_with_drivers( databases=databases, auto_download=auto_download, manual_classpath=manual_classpath or None, )
[docs] def start_jvm( classpath: list[Path] | None = None, jvm_path: Path | None = None, jvm_args: list[str] | None = None, auto_download: bool = True, databases: list[str] | None = None, ) -> None: """ Start the JVM with specified classpath and arguments. Args: classpath: List of paths to add to classpath. If None, uses environment + auto-download. jvm_path: Path to JVM library. If None, JPype will auto-detect. jvm_args: Additional JVM arguments (e.g., ['-Xmx512m']). auto_download: Whether to auto-download JDBC drivers. Default: True. databases: List of database names for auto-download. If None, downloads common drivers. Raises: JVMNotStartedError: If JVM fails to start. """ global _jvm_started if _jvm_started: logger.debug("JVM already started") return try: import jpype except ImportError as e: raise JVMNotStartedError( "JPype is not installed. Install with: pip install JPype1" ) from e if jpype.isJVMStarted(): _jvm_started = True logger.debug("JVM already started by another process") return # Build classpath if classpath is None: classpath = get_classpath(auto_download=auto_download, databases=databases) if not classpath: logger.warning( "No JDBC drivers found in classpath. " "Set CLASSPATH environment variable or enable auto_download." ) classpath_str = os.pathsep.join(str(p) for p in classpath) # Build JVM arguments args = jvm_args or [] if classpath_str: args.append(f"-Djava.class.path={classpath_str}") # Start JVM try: if jvm_path: jpype.startJVM(str(jvm_path), *args, convertStrings=True) else: jpype.startJVM(*args, convertStrings=True) _jvm_started = True logger.info("JVM started successfully") logger.debug(f"Classpath: {classpath_str}") logger.debug(f"JVM args: {args}") if classpath: logger.info(f"Loaded {len(classpath)} JDBC driver(s)") except Exception as e: raise JVMNotStartedError(f"Failed to start JVM: {e}") from e
[docs] def is_jvm_started() -> bool: """Check if JVM is started.""" try: import jpype return jpype.isJVMStarted() except ImportError: return False
[docs] def shutdown_jvm() -> None: """Shutdown the JVM (optional, usually not needed).""" global _jvm_started try: import jpype if jpype.isJVMStarted(): jpype.shutdownJVM() _jvm_started = False logger.info("JVM shutdown") except ImportError: pass
[docs] def get_java_class(class_name: str) -> Any: """ Get a Java class by name. Args: class_name: Fully qualified Java class name. Returns: Java class object. Raises: JVMNotStartedError: If JVM is not started. """ if not is_jvm_started(): raise JVMNotStartedError("JVM is not started. Call start_jvm() first.") try: import jpype return jpype.JClass(class_name) except Exception as e: raise JVMNotStartedError(f"Failed to load Java class {class_name}: {e}") from e