Source code for eta_utility.util_julia

from __future__ import annotations

import importlib
import inspect
import os
import pathlib
import subprocess
import sys
from shutil import which
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from types import ModuleType

    from eta_utility.type_hints import Path

# Set environment variable to determine the correct python environment to use when calling back into
# python from julia
os.environ["PYCALL_JL_RUNTIME_PYTHON"] = sys.executable

JULIA_NOT_FOUND_MSG = (
    "Julia executable cannot be found. "
    "If you have installed Julia, make "
    "sure Julia executable is in the system path. "
    "If you have not installed Julia, download from "
    "https://julialang.org/downloads/ and install it. "
)


[docs] def import_jl_file(filename: Path) -> ModuleType: check_julia_package() _filename = pathlib.Path(filename) if not isinstance(filename, pathlib.Path) else filename jl = importlib.import_module("julia.Main") jl.include(_filename.absolute().as_posix()) return jl
[docs] def import_jl(importstr: str) -> ModuleType: """Import a julia file into the main namespace. The syntax is equivalent to python import strings. If the import string starts with a '.', the import path is interpreted as relative to the file calling this function. If the import string is absolute, it will use the python sys.path list to look for the file. The function also makes sure that julia.Main is imported and returns a handle to the module. This way, the imported julia file can be used right away. :param importstr: Path to the imported julia package. If the path starts with a '.' this will be relative to the file it is specified in. Otherwise, this will look through python import Path. """ check_julia_package() file = importstr_to_path(importstr, _stack=2) jl = import_jl_file(file) return jl
[docs] def importstr_to_path(importstr: str, _stack: int = 1) -> pathlib.Path: """Converts an import string into a python path. The syntax is equivalent to python import strings. If the import string starts with a '.', the import path is interpreted as relative to the file calling this function. If the import string is absolute, it will use the python sys.path list to look for the file. :param importstr: Path to the imported julia package (python import string). If the path starts with a '.' this will be relative to the file it is specified in. Otherwise, this will look through the python import paths. """ if len(importstr) > 2 and importstr[0] == "." and importstr[1] == ".": pathstr = f"..{importstr[2:].replace('.', '/')}.jl" relative = True elif len(importstr) > 1 and importstr[0] == ".": pathstr = f"{importstr[1:].replace('.', '/')}.jl" relative = True else: pathstr = f"{importstr.replace('.', '/')}.jl" relative = False file = None found = False if relative: file = pathlib.Path(inspect.stack()[_stack].filename).parent / pathstr if file.is_file(): found = True else: for path in sys.path: file = pathlib.Path(path) / pathstr if file.is_file(): found = True break if not found and relative and file: raise ImportError(f"Could not find the specified julia file. Looking for {file}") elif not found or not file: raise ImportError(f"Could not find the specified julia file. Looking for {pathstr}") return file
[docs] def update_agent() -> None: """Upadtes the NSGA2 agent model file""" import tempfile from test.test_etax.test_agents import TestNSGA2 cls = TestNSGA2() cls.create_stored_agent_file("test/resources/agents/", tempfile.TemporaryDirectory().name)
[docs] def install_julia() -> None: """Checks if Julia language is available in the system and install and configure pyjulia. Also install ju_extensions in Julia environmnent. """ if which("julia") is None: raise ImportError(JULIA_NOT_FOUND_MSG) try: import julia # noqa: I900 except ImportError: subprocess.check_call([sys.executable, "-m", "pip", "install", "julia"]) import julia # noqa: I900 # Set environment variable to determine the correct python environment to use when calling back into # python from julia os.environ["PYCALL_JL_RUNTIME_PYTHON"] = sys.executable julia.install() subprocess.check_call( [ "julia", "-e", f"import Pkg; Pkg.develop(path=\"{(pathlib.Path(__file__).parent / 'ju_extensions').as_posix()}\")", ] )
[docs] def check_julia_package() -> bool: """Check if everything is available and setup correctly to execute modules depending on eta_utility julia extensions. This function raises ImportError if necessary components are missing. :returns: True if is installed ImportError if not """ if which("julia") is None: raise ImportError(JULIA_NOT_FOUND_MSG) try: import julia # noqa: I900 except ModuleNotFoundError: raise ImportError( "Could not find the python julia package. Please run the command: install-julia " "inside the python virtual environment where eta-utility is installed." ) try: from julia import ju_extensions # noqa: I900 F401 except julia.core.UnsupportedPythonError: raise ImportError( "PyCall for Julia is installed for a different python binary than you are currently " "using. Please run the command: install-julia inside the python virtual environment " "where eta-utility is installed." ) except (ModuleNotFoundError, ImportError, AttributeError) as e: raise ImportError( "Could not find julia extension module for eta_utility (ju_extensions missing). Please " "run the command: install-julia inside the python virtual environment where eta-utility " "is installed." ) from e return True
[docs] def julia_extensions_available() -> bool: """Check if everything is available and setup correctly to execute modules depending on eta_utility julia extensions. This function returns false if necessary components are missing. It does not provide any indications what is missing. :return: True if julia extensions are correctly installed, false if not. """ try: check_julia_package() except ImportError: return False return True